精品SpringSecurity集成JWT#yyds干货盘点#

将相本无种,男儿当自强。这篇文章主要讲述精品SpringSecurity集成JWT#yyds干货盘点#相关的知识,希望能为你提供帮助。
本博客代码下载地址:

  • https://gitee.com/hcitlife/springsecutiry-jwt-demo 0、准备工作创建SpringBoot中项目:
  • Maven依赖:
< dependency> < groupId> org.projectlombok< /groupId> < artifactId> lombok< /artifactId> < version> 1.18.22< /version> < /dependency> < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-web< /artifactId> < /dependency> < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-test< /artifactId> < scope> test< /scope> < /dependency> < dependency> < groupId> org.springframework.security< /groupId> < artifactId> spring-security-test< /artifactId> < scope> test< /scope> < /dependency> < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-security< /artifactId> < /dependency> < dependency> < groupId> io.jsonwebtoken< /groupId> < artifactId> jjwt-api< /artifactId> < version> 0.11.2< /version> < /dependency> < dependency> < groupId> io.jsonwebtoken< /groupId> < artifactId> jjwt-impl< /artifactId> < version> 0.11.2< /version> < scope> runtime< /scope> < /dependency> < dependency> < groupId> io.jsonwebtoken< /groupId> < artifactId> jjwt-jackson< /artifactId> < version> 0.11.2< /version> < scope> runtime< /scope> < /dependency>

  • application.yml
jwt: # 为JWT基础信息加密和解密的密钥,长度需要大于等于43 # 在实际生产中通常不直接写在配置文件里面。而是通过应用的启动参数传递,并且需要定期修改 secret: oQZSeguYloAPAmKwvKqqnifiQatxMEPNOvtwPsCLasd # JWT令牌的有效时间,单位秒,默认2周 expiration: 1209600 header: Authorization spring: main: allow-circular-references: true# 允许循环注入

项目使用SpringBoot版本为2.6.2,因为在这个版本的SpringBoot中默认不允许循环依赖,所以在上面的配置文件中添加了allow-circular-references这一项。
1、创建Jwt工具类
  • 工具类
@Slf4j @Component //@ConfigurationProperties(prefix = "jwt") public class JwtUtil /** * 携带JWT令牌的HTTP的Header的名称,在实际生产中可读性越差越安全 */ @Getter @Value("$jwt.header") private String header; /** * 为JWT基础信息加密和解密的密钥 * 在实际生产中通常不直接写在配置文件里面。而是通过应用的启动参数传递,并且需要定期修改。 */ @Value("$jwt.secret") private String secret; /** * JWT令牌的有效时间,单位秒 * - 默认2周 */ @Value("$jwt.expiration") private Long expiration; /** * SecretKey 根据 SECRET 的编码方式解码后得到: * Base64 编码:SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString)); * Base64URL 编码:SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretString)); * 未编码:SecretKey key = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8)); */ private static SecretKey getSecretKey(String secret) byte[] encodeKey = Decoders.BASE64.decode(secret); return Keys.hmacShaKeyFor(encodeKey); /** * 用claims生成token * * @param claims 数据声明,用来创建payload的私有声明 * @return token 令牌 */ private String generateToken(Map< String, Object> claims) SecretKey key = getSecretKey(secret); //SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); //两种方式等价// 添加payload声明 JwtBuilder jwtBuilder = Jwts.builder() // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setId(UUID.randomUUID().toString()) // iat: jwt的签发时间 .setIssuedAt(new Date())// 你也可以改用你喜欢的算法,支持的算法详见:https://github.com/jwtk/jjwt#features // SignatureAlgorithm.HS256:指定签名的时候使用的签名算法,也就是header那部分 .signWith(key, SignatureAlgorithm.HS256) .setExpiration(new Date(System.currentTimeMillis() + this.expiration * 1000)); String token = jwtBuilder.compact(); return token; /** * 生成Token令牌 * * @param userDetails 用户 * @return 令牌Token */ public String generateToken(UserDetails userDetails) Map< String, Object> claims = new HashMap< > (); claims.put("sub", userDetails.getUsername()); claims.put("created", new Date()); return generateToken(claims); /** * 从token中获取数据声明claim * * @param token 令牌token * @return 数据声明claim */ public Claims getClaimsFromToken(String token) try SecretKey key = getSecretKey(secret); Claims claims = Jwts.parser() .setSigningKey(key) .parseClaimsJws(token) .getBody(); return claims; catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) log.error("token解析错误", e); throw new IllegalArgumentException("Token invalided."); public String getUserRole(String token) return (String) getClaimsFromToken(token).get("role"); /** * 从token中获取登录用户名 * * @param token 令牌 * @return 用户名 */ public String getSubjectFromToken(String token) String subject; try Claims claims = getClaimsFromToken(token); subject = claims.getSubject(); catch (Exception e) subject = null; return subject; /** * 获取token的过期时间 * * @param token token * @return 过期时间 */ public Date getExpirationFromToken(String token) return getClaimsFromToken(token).getExpiration(); /** * 判断token是否过期 * * @param token 令牌 * @return 是否过期:已过期返回true,未过期返回false */ public Boolean isTokenExpired(String token) Date expiration = getExpirationFromToken(token); return expiration.before(new Date()); /** * 验证令牌:判断token是否非法 * * @param token令牌 * @param userDetails 用户 * @return 如果token未过期且合法,返回true,否则返回false */ public Boolean validateToken(String token, UserDetails userDetails) //如果已经过期返回false if (isTokenExpired(token)) return false; String usernameFromToken = getSubjectFromToken(token); String username = userDetails.getUsername(); return username.equals(usernameFromToken);

  • 测试代码
@SpringBootTest public class JwtUtilTest @Resource private JwtUtil jwtUtil; @Resource private PasswordEncoder passwordEncoder; @Test void fun() System.out.println(passwordEncoder); SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256); System.out.println(secretKey); //生成token @Test void generateToken() //用户信息 String encode = passwordEncoder.encode("1234"); User user = new User("zhangsan", encode, AuthorityUtils.createAuthorityList()); String token = jwtUtil.generateToken(user); System.out.println(token); @Test void getClaimsFromToken() //用户信息 String encode = passwordEncoder.encode("1234"); User user = new User("zhangsan", encode, AuthorityUtils.createAuthorityList()); String token = jwtUtil.generateToken(user); System.out.println(token); Claims claims = jwtUtil.getClaimsFromToken(token); System.out.println(claims); @Test void getSubjectFromToken() //用户信息 String encode = passwordEncoder.encode("1234"); User user = new User("zhangsan", encode, AuthorityUtils.createAuthorityList()); String token = jwtUtil.generateToken(user); System.out.println(token); String username = jwtUtil.getSubjectFromToken(token); System.out.println(username); @Test void getExpirationFromToken() //用户信息 String encode = passwordEncoder.encode("1234"); User user = new User("zhangsan", encode, AuthorityUtils.createAuthorityList()); String token = jwtUtil.generateToken(user); System.out.println(token); Date date = jwtUtil.getExpirationFromToken(token); System.out.println(new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(date)); @Test void isTokenExpired() //用户信息 String encode = passwordEncoder.encode("1234"); User user = new User("zhangsan", encode, AuthorityUtils.createAuthorityList()); String token = jwtUtil.generateToken(user); System.out.println(token); Boolean res = jwtUtil.isTokenExpired(token); System.out.println(res); @Test void validateToken() //用户信息 String encode = passwordEncoder.encode("1234"); User user = new User("zhangsan", encode, AuthorityUtils.createAuthorityList()); String token = jwtUtil.generateToken(user); System.out.println(token); User user2 = new User("zhangsan", "", AuthorityUtils.createAuthorityList()); Boolean res = jwtUtil.validateToken(token, user2); System.out.println(res); //模拟篡改 @Test void fake() // 将我改成你生成的token的第一段(以.为边界) String encodedHeader = "eyJhbGciOiJIUzI1NiJ9"; // 测试4: 解密Header byte[] header = Base64.decodeBase64(encodedHeader.getBytes()); System.out.println(new String(header)); // 将我改成你生成的token的第二段(以.为边界) String encodedPayload = "eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk1NDEsImV4cCI6MTU2Njc5OTE0MX0"; // 测试5: 解密Payload byte[] payload = Base64.decodeBase64(encodedPayload.getBytes()); System.out.println(new String(payload)); //用户信息 String encode = passwordEncoder.encode("1234"); User user = new User("zhangsan", encode, AuthorityUtils.createAuthorityList()); // 测试6: 这是一个被篡改的token,因此会报异常,说明JWT是安全的 jwtUtil.validateToken("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk3MzIsImV4cCI6MTU2Njc5OTMzMn0.nDv25ex7XuTlmXgNzGX46LqMZItVFyNHQpmL9UQf-aUx", user);

2、创建没有权限时,Jwt拒绝访问的处理器
/** * 当用户在没有授权的时候,返回的指定信息 */ @Slf4j @Component public class jwtAccessDeniedHandler implements AccessDeniedHandler @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws ServletException log.info("用户访问没有授权资源:",e.getMessage()); response.setContentType("application/json; charset=utf-8"); response.setCharacterEncoding("utf-8"); try(PrintWriter out = response.getWriter(); ) Result result = ResultUtil.fail("用户访问未授权资源").setCode(HttpServletResponse.SC_UNAUTHORIZED); out.write(JsonUtil.obj2String(result)); out.flush(); catch (IOException exception)

3、创建没有token时,Jwt的EntryPoint
/** *用户访问资源没有携带正确的token,时返回的信息 */ @Slf4j @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws ServletException, IOException log.info("用户访问资源没有携带正确的token:",e.getMessage()); response.setContentType("application/json; charset=utf-8"); response.setCharacterEncoding("utf-8"); try(PrintWriter out = response.getWriter(); ) Result result = ResultUtil.fail("用户访问资源没有携带正确的token").setCode(HttpServletResponse.SC_UNAUTHORIZED); out.write(JsonUtil.obj2String(result)); out.flush(); catch (IOException exception)

4、创建UserDetailsService
@Service public class UserDetailsServiceImpl implements UserDetailsService @Resource private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException //直接写死数据信息,可以在这里获取数据库的信息并进行验证 //UserDetails user = User.withUsername(username) //.password(passwordEncoder.encode("1234")) //.authorities("Role_vip,user:list,user:update") //.build(); User user = new User(username, passwordEncoder.encode("1234"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_vip,user:list,user:update")); return user;

5、创建Jwt认证过滤器
@Slf4j @Component public class JwtAuthenticationFilter extends OncePerRequestFilter @Resource private JwtUtil jwtUtil; @Resource private UserDetailsServiceImpl userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException String token = request.getHeader(jwtUtil.getHeader()); log.info("header token:", token); //如果请求头中有token,则进行解析,并且设置认证信息 if (token != null & & token.trim().length() > 0) //根据token获取用户名 String username = jwtUtil.getSubjectFromToken(token); // 验证username,如果验证合法则保存到SecurityContextHolder if (username != null & & SecurityContextHolder.getContext().getAuthentication() == null) UserDetails userDetails = userDetailsService.loadUserByUsername(username); // JWT验证通过,使用Spring Security 管理 if (jwtUtil.validateToken(token, userDetails)) //加载用户、角色、权限信息 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); //如果请求头中没有Authorization信息则直接放行 chain.doFilter(request, response);

6、配置SpringSecurity
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SpringSecurityJwtConfig extends WebSecurityConfigurerAdapter @Bean public PasswordEncoder passwordEncoder() return new BCryptPasswordEncoder(); @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception return super.authenticationManagerBean(); @Resource private com.hc.jwt.jwtAccessDeniedHandler jwtAccessDeniedHandler; @Resource private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Resource private JwtAuthenticationFilter jwtAuthenticationFilter; @Override protected void configure(HttpSecurity http) throws Exception http.csrf().disable(); // 禁用session http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests() //login 不拦截 .antMatchers("/login").permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers("/").permitAll() .anyRequest().authenticated(); //用户访问没有授权资源 http.exceptionHandling().accessDeniedHandler(jwtAccessDeniedHandler); //授权错误信息处理 //用户访问资源没有携带正确的token http.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint); // 使用自己定义的拦截机制验证请求是否正确,拦截jwt http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

7、创建控制器
@RestController public class UserController @Resource private JwtUtil jwtUtil; @Resource private UserDetailsServiceImpl userDetailsService; @PostMapping("/login") public String login(@RequestBody UserVO userVO) //生成token,返回给客户端 UserDetails userDetails = userDetailsService.loadUserByUsername(userVO.getUsername()); String token = jwtUtil.generateToken(userDetails); return token; @GetMapping("/fun1") @PreAuthorize("hasRole(\\"vip\\")") public Result fun1() return ResultUtil.success("fun1"); @GetMapping("/fun2") @PreAuthorize("hasRole(\\"admin\\")") public Result fun2() return ResultUtil.success("fun1"); @GetMapping("/fun3") @PreAuthorize("hasAuthority(\\"user:list\\")") public Result fun3() return ResultUtil.success("fun1"); @GetMapping("/fun4") @PreAuthorize("hasAuthority(\\"user:delete\\")") public Result fun4() return ResultUtil.success("fun1");

结果
  • 用户登录
    精品SpringSecurity集成JWT#yyds干货盘点#

    文章图片
  • 【精品SpringSecurity集成JWT#yyds干货盘点#】角色
    精品SpringSecurity集成JWT#yyds干货盘点#

    文章图片

  • 权限
    精品SpringSecurity集成JWT#yyds干货盘点#

    文章图片
其他代码
  • UserVO
@Getter @Setter public class UserVO private String username; private String password;


    推荐阅读