Springroo:使用双重身份验证逻辑:会话和令牌

时间:2019-03-08 13:27:16

标签: spring security session token

我正在做一个新的Web应用程序,该应用程序分为两个部分:

  1. API +后台(Java部分)
  2. 客户端(JS部分)

第一个项目1.是一个经典的Springroo项目,使用了MVC和安全功能。其目的是向管理员提供一些UI和路由,以便他们管理数据库。 通过经典的登录/传递表单(由Springroo自动生成)来对它们进行认证,并且认证使用会话。 这是applicationContext-security.xml的内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:beans="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
    <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
        <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"
        />
        <logout logout-url="/resources/j_spring_security_logout" />
        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/users/**" access="hasRole('ADM')" />
        <intercept-url pattern="/heroperiods/**" access="hasRole('USER')" />
        <intercept-url pattern="/karacters/**" access="hasRole('ADM')" />
        <intercept-url pattern="/karacterskillses/**" access="hasRole('ADM')" />
        <intercept-url pattern="/karactergpses/**" access="hasRole('ADM')" />

        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/static/**" access="permitAll" />
        <intercept-url pattern="/login/**" access="permitAll" />
        <intercept-url pattern="/players/login" access="permitAll" />
        <intercept-url pattern="/players/new" access="permitAll" />
        <intercept-url pattern="/**" access="isAuthenticated()" />
        <!-- Concurrent Session Control -->
        <session-management session-authentication-error-url="/sessionExpired" >
            <concurrency-control max-sessions="1"/>
        </session-management>
    </http>
    <!-- Configure Authentication mechanism -->
    <beans:bean name="adminAuthenticationProvider" class="com.ksfadventure.security.AdminAuthenticationProvider">
    </beans:bean>

    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="adminAuthenticationProvider" />
    </authentication-manager>
</beans:beans>

这部分使用的是AdminAuthenticationProvider,它扩展了AbstractUserDetailsAuthenticationProvider。

public class AdminAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub      
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {

        String password = authentication.getCredentials().toString();
        AccessRight account = AccessRightController.getAccessRight(username, password);     
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
        if(null == account) 
            throw new BadCredentialsException("Wrong login or password");

        authList.add(new SimpleGrantedAuthority(account.getAccessRightRole()));

        return new User(username, account.getAccessRightPassword(), true, true, true, true, authList);
    }

}

这一切进展顺利。

现在,我需要添加一个基于无状态令牌的身份验证(用于传入的JS客户端项目),供最终用户使用。

@RequestMapping("/players")
@Controller
@RooWebScaffold(path = "players", formBackingObject = Player.class)
public class PlayerController {

    private TokenManager tokenManager = new TokenManager();

    // (...)

    /**
     * Authenticate a user
     */
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public ResponseEntity<String> authenticate(@RequestBody String authParams) {
        ObjectMapper mapper = new ObjectMapper();
        // (...)
        WebServiceAnswer answer = new WebServiceAnswer();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json; charset=utf-8");

        // JSON to Object
        try {
            player = mapper.readValue(authParams, Player.class);
            // (...)
        } catch (IOException e) {
            // (...)
        }
        if(null == player) return new ResponseEntity<String>(headers, HttpStatus.BAD_REQUEST);

        // get and check credentials
        email = player.getPlayerMail();
        password = player.getPlayerPassword();
        player = PlayerController.getPlayer(email, password);
        if (null == player) {
            // (...)
            return new ResponseEntity<String>(answer.toJsonString(), headers, HttpStatus.NOT_FOUND);
        }
        // generate token
        token = tokenManager.generate(player);
        // (...)
        return new ResponseEntity<String>(answer.toJsonString(), headers, HttpStatus.ACCEPTED);     
    }

    public static Player getPlayer(String email, String password) {
        // get the expected account
        Player player = Player.findPlayer(email);
        if (null == player) return null;
        // (...)
        String accountPassword = player.getPlayerPassword();
        // (...)
        // check password
        if (BCrypt.checkpw(password, accountPassword)) {
            // (...)
            return player;
        }
        // (...)
        return null;
    } // end of method getAccessRight

}

这是TokenManager类。

@Component
public class TokenManager {

    private final String secretKey = "ilovestackoverflow";

    public String generate(Player player) {
        Claims claims = Jwts.claims().setSubject(player.getPlayerMail());
        claims.put("playerId", String.valueOf(player.getPlayerId()));
        claims.put("role", Roles.getRoleUser());

        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, this.secretKey)
                .compact();
    }

    public Player validate(String token) {
        Player player = null;
        try {
            Claims body = Jwts.parser()
                    .setSigningKey(this.secretKey)
                    .parseClaimsJws(token)
                    .getBody();
            player = Player.findPlayer(body.getSubject());
            // maybe manage a role here ???
            // claims.put("role", jwtUser.getRole());
        }
        catch (Exception e) {
            System.out.println(e);
        }
        return player;
    }

}

要尝试将“ / players / *”路由呼叫作为可能的登录路由进行管理,我创建了TokenConfig类。

//@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableWebSecurity
@Configuration
public class TokenConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PlayerAuthenticationProvider playerAuthenticationProvider;

    @Bean
    public AuthenticationManager authenticationManager() {
        List<AuthenticationProvider> providerList = Collections.singletonList((AuthenticationProvider)playerAuthenticationProvider);
        ProviderManager pm = new ProviderManager(providerList);
        return pm;
    }

    //create a custom filter(this is ran at Tomcat start)
    @Bean
    public TokenFilter authTokenFilter() {
        TokenFilter filter = new TokenFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
        .authorizeRequests().antMatchers("**/**").authenticated()
        .and()                  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(authTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        http.headers().cacheControl();
    }

}

它使用扩展了AbstractUserDetailsAuthenticationProvider的PlayerAuthenticationProvider。

@Component
public class PlayerAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private TokenManager tokenManger;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub      
    }

    @Override
    protected UserDetails retrieveUser(String userMail, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {            
        JwtAuthToken jwtAuthenticationToken = (JwtAuthToken) authentication;
        String token = jwtAuthenticationToken.getToken();
        Player account = tokenManger.validate(token);
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
        if(null == account) 
            throw new BadCredentialsException("Wrong login or password");

        authList.add(new SimpleGrantedAuthority(Roles.getRoleUser()));

        return new User(userMail, account.getPlayerPassword(), true, true, true, true, authList);
    }
}

最后是TokenFilter。

public class TokenFilter extends AbstractAuthenticationProcessingFilter {

    public TokenFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    public TokenFilter() {
        super("/**");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        String header = request.getHeader("Authorisation");

        if(header == null || !header.startsWith("Check ")) {
            throw new RuntimeException(" Token is missing");
        }

        String authenticationToken = header.substring(6);

        JwtAuthToken token = new JwtAuthToken(authenticationToken);
        return getAuthenticationManager().authenticate(token);
    }

}

当我尝试使用POST查询登录“ / players / login”时,所有这些使我可以获得令牌。 JSON内容按预期返回,但是问题是我不知道如何为将来的客户端调用管理此令牌。

如果我尝试到达其他路由(例如,/ heroperiods / {i}),则服务器会检查我是否没有活动的(管理员)会话,并要求我进行身份验证。

是否存在现有的Java流程来管理每个查询的客户令牌? 要么 : 我是否应该手动打开所有路线并一一管理? (我的意思是检查令牌是否有效) 如果是这样,我是否可以在全球范围内检查所有现有路线的令牌状态? 我应该将令牌存储在播放器表中并更新令牌及其最后使用日期吗?

[编辑] 当我调试应用程序时,从不使用TokenConfig.configure()方法。如果我添加@EnableGlobalMethodSecurity(prePostEnabled = true)批注,则会出现异常,因为有两个authenticationManager而不是一个。 [/ EDIT]

0 个答案:

没有答案