标签: Security

  • Spring Security从表单验证到token验证

    对于任何需要认证的系统,Spring Security无疑是一个很好的选择。

    无论是从认证本身,还是权限控制,Spring Security都有极好的支持。

    Form登录是一种极为常见的方式,用户输入用户名和密码,post登录信息,认证之后登录信息保存在session之中。

    但是随着架构演进,前后端逐渐分离,之前的大部分逻辑都变成了API供前端调用,而Form登录逐渐成为累赘,调试上的困难。这个时候就可以考虑换为token模式,进而将后端API stateless化。

    Token的生成和解析

    第一步是选择token的生成和解析,我们来个简单的,token中只包含加密后的用户名,具体生成才是JWT的HS512加密。

    先配置一个登录成功后的响应AuthSuccessHandler

    public class AuthSuccessHandler implements AuthenticationSuccessHandler {
        public static final String SECRET_KEY = "0Qx*@S7q";
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                            Authentication authentication) throws IOException, ServletException {
            response.setStatus(HttpStatus.OK.value());
            JsonNodeFactory factory = JsonNodeFactory.instance;
            ObjectNode objectNode = factory.objectNode();
            objectNode.set("status", factory.textNode("success"));
            objectNode.set("token", factory.textNode(createToken(authentication)));
            PrintWriter out = response.getWriter();
            out.write(objectNode.toString());
            out.close();
        }
    
        private String createToken(Authentication authentication) {
            return Jwts.builder()
                    .setSubject(authentication.getName())
                    .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                    .compact();
        }
    }

    配置中添加一句

    and().formLogin().successHandler(new AuthSuccessHandler())

    这样登录成功后会返回类似这样的json

    {
        "status": "success",
        "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ4aWFuIn0.O5xEDP5JCMcrQzpRaswsrhWfZ1Wlw7r8-94KU14G6wN_nSKH0YZZprKaoiuiNIRXWbmkX68KFx2fts1DPbPnOw"
    }

    Token认证

    现在我们有了token,任何消费API的请求都需要在header中包含这个token,然后再根据这个token给予用户的请求完成认证,Spring Security的Pre-Auth就是合适的方案。

    当我们获取token以后直接解码获得用户名,并给予当前访问认证。

    final String token = request.getHeader(AUTH_HEADER_NAME);
            if (!StringUtils.isEmpty(token)) {
                String username = Jwts.parser()
                        .setSigningKey(AuthSuccessHandler.SECRET_KEY)
                        .parseClaimsJws(token)
                        .getBody()
                        .getSubject();
                if (username!= null) {
                    logger.info(String.format("User login in with user name: %s", username));
                    return new UsernamePasswordAuthenticationToken(username, "", Lists.<GrantedAuthority>newArrayList());
                }
            }
            return null;

    这个一个Filter,它的位置位于UsernamePasswordAuthenticationFilter之前,在配置中直接配置

    .addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    这样就完成了token的认证,只要请求包含有效的token,那么就能访问授权后的资源。

    其他

    这只是一个简单的例子,token的生成方式非常脆弱,也没有过期超时等等的验证。Spring Security还有oauth模块,可以使用其中的tokenStore等等去完善。