SpringBoot2.x+SpringSecurit+JWT(三)前后端分离进阶

此次文章是上一篇文章进阶片,以Token的方式进行验证,Token的生成策略是JWT,如有不懂请看上一篇文章SpringBoot2.x+SpringSecurit(二)前后端分离

什么是JWT,为什么使用JWT?

  • JWT是 Json Web Token 的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的小巧和 自包含的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。
  • 使用 JWT 做权限验证,相比Session的优点是,Session 需要占用大量服务器内存,并且在多服务器时就会涉及到共享Session问题,而JWT无需存储在服务器,不占用服务器资源(也就是无状态的),用户在登录后拿到 Token 后,访问需要权限的请求时附上Token(一般设置在Http请求头)。

工作流程

  1. 输入用户名、密码,进行登录
  2. 服务器验证登录鉴权,如果用户合法,根据用户的信息生成JWT Token
  3. 服务器将该token以json形式返回(不一定要json形式)
  4. 前端得到token,储存起来
  5. 以后用户请求API时,在请求的header中加入 Authorization:
    Bearer xxxx(token)。此处 注意token之前有一个7长度字符的Bearer 。
  6. 服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。

1. 引入依赖

1
2
//jwt
compile 'io.jsonwebtoken:jjwt:0.9.0'

2. Security 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthSuccessHandler authSuccessHandler;

    @Autowired
    private AuthFailHandler authFailHandler;


    @Autowired
    private AuthAccessDeniedHandler authAccessDeniedHandler;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry=http.authorizeRequests();
        for (String u:PermitUrl.permitUrls) {
            registry.antMatchers(u).permitAll();
        }
        registry.and()
                .formLogin()
                .loginPage("/u/noLogin")
                .loginProcessingUrl("/login")
                .permitAll()
                //成功处理类
                .successHandler(authSuccessHandler)
                //失败处理类
                .failureHandler(authFailHandler)
                .and()
                .logout()
                .permitAll()
                .and()
                .authorizeRequests()
                //任何请求
                .anyRequest()
                //都需要认证
                .authenticated()
                .and()
                //关闭跨站请求防护
                .csrf().disable()
                //前后端分离采用JWT 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //自定义权限拒绝处理类
                .exceptionHandling().accessDeniedHandler(authAccessDeniedHandler)
                .and()
                //添加JWT过滤器 除/login其它请求都需经过此过滤器
                .addFilter(new JwtAuthenticationFilter(authenticationManager()));
    }
}

这里不需要session储存了,以及重现验证机制

3. 成功配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Slf4j
@Component
public class AuthSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //获取登陆成功用户名
        String username = ((UserDetails)authentication.getPrincipal()).getUsername();
        log.info("登陆成功:{}",username);
        List<String> authorities=((UserDetails) authentication.getPrincipal()).getAuthorities().stream().map(a->new String(((GrantedAuthority) a).getAuthority())).collect(Collectors.toList());
        //登陆成功生成JWT
        String token = Jwts.builder()
                //主题 放入用户名
                .setSubject(username)
                //自定义属性 放入用户拥有权限
                .claim(SecurityConstant.AUTHORITIES.getCode(), JSONObject.toJSONString(authorities))
                //失效时间 7天
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 *7))
                //签名算法和密钥
                .signWith(SignatureAlgorithm.HS512,SecurityConstant.JWT_SIGN_KEY.getCode())
                .compact();
        token = SecurityConstant.TOKEN_SPLIT.getCode() + token;

        String msg=JSON.toJSONString(ServerResponse.Success(token));
        ServerResponse.out(response,msg);

    }
}

登陆成功后获取用户信息,以及权限,以JWT的HS512算法生成Token 然后以json返回给前端。
注意:如果你是JDK1.8以上需要加入compile 'javax.xml.bind:jaxb-api:2.3.0'依赖

4. 失败配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
@Slf4j
public class AuthFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        String msg=null;
        if (exception instanceof UsernameNotFoundException || exception instanceof BadCredentialsException) {
            msg="用户名或密码输入错误,登录失败!";
        } else if (exception instanceof DisabledException) {
           msg="账户被禁用,登录失败,请联系管理员!";
        } else {
            msg="登录失败!";
        }

        msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),msg));
        ServerResponse.out(response,msg);
    }

}

5. token校验类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@Slf4j
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader(SecurityConstant.HEADER.getCode());
        if (header == null || !header.startsWith(SecurityConstant.TOKEN_SPLIT.getCode())) {
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader(SecurityConstant.HEADER.getCode());
        if (token!=null){
            Claims claims = null;
            try {
                claims=Jwts.parser()
                        .setSigningKey(SecurityConstant.JWT_SIGN_KEY.getCode())
                        .parseClaimsJws(token.replace(SecurityConstant.TOKEN_SPLIT.getCode(), ""))
                        .getBody();
                //获取用户名
                String username = claims.getSubject();

                //获取权限(角色)
                List<GrantedAuthority> authorities=null;
                String authority = claims.get(SecurityConstant.AUTHORITIES.getCode()).toString();

                if(authority!=null){
                    List<String> list = JSONObject.parseArray(authority,String.class);
                    authorities= list.stream().map(a->new SimpleGrantedAuthority(a)).collect(Collectors.toList());

                }
                if(username!=null) {
                    //此处password不能为null
                    User user=new User(username,"",authorities);
                    return new UsernamePasswordAuthenticationToken(user, null, authorities);
                }

            }catch (ExpiredJwtException e) {
                logger.error("Token已过期: {} " + e);
                String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"Token已过期"));
                ServerResponse.out(response,msg);
            } catch (UnsupportedJwtException e) {
                logger.error("Token格式错误: {} " + e);
                String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"Token格式错误"));
                ServerResponse.out(response,msg);
            } catch (MalformedJwtException e) {
                logger.error("Token没有被正确构造: {} " + e);
                String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"Token没有被正确构造"));
                ServerResponse.out(response,msg);
            } catch (SignatureException e) {
                logger.error("签名失败: {} " + e);
                String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"签名失败"));
                ServerResponse.out(response,msg);
            } catch (IllegalArgumentException e) {
                logger.error("非法参数异常: {} " + e);
                String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"非法参数异常"));
                ServerResponse.out(response,msg);
            }
        }
        return null;
    }
}

这里判断请求的header格式是否正确,如正确,解析Token获取用户信息

6. 测试

输入图片说明
输入图片说明
如果访问其他API
输入图片说明
这里的值为你登录的Bearer +Token。

7. 总结

源码请看:源码
这里以JWT生成Token和储存Token,那么如何以redis储存呢?

转载于:https://my.oschina.net/u/4037000/blog/3045349

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值