Springboot集成Spring Security实现JWT认证的步骤详解

2025-05-29 0 83

1 简介

Spring Security作为成熟且强大的安全框架,得到许多大厂的青睐。而作为前后端分离的SSO方案,JWT也在许多项目中应用。本文将介绍如何通过Spring Security实现JWT认证。

用户与服务器交互大概如下:

Springboot集成Spring Security实现JWT认证的步骤详解

  1. 客户端获取JWT,一般通过POST方法把用户名/密码传给server;
  2. 服务端接收到客户端的请求后,会检验用户名/密码是否正确,如果正确则生成JWT并返回;不正确则返回错误;
  3. 客户端拿到JWT后,在有效期内都可以通过JWT来访问资源了,一般把JWT放在请求头;一次获取,多次使用;
  4. 服务端校验JWT是否合法,合法则允许客户端正常访问,不合法则返回401。

2 项目整合

我们把要整合的Spring SecurityJWT加入到项目的依赖中去:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-security</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>io.jsonwebtoken</groupId>
  11. <artifactId>jjwt</artifactId>
  12. <version>0.9.1</version>
  13. </dependency>

2.1 JWT整合

2.1.1 JWT工具类

JWT工具类起码要具有以下功能:

  • 根据用户信息生成JWT
  • 校验JWT是否合法,如是否被篡改、是否过期等;
  • JWT中解析用户信息,如用户名、权限等;

具体代码如下:

  1. @Component
  2. public class JwtTokenProvider {
  3. @Autowired JwtProperties jwtProperties;
  4. @Autowired
  5. private CustomUserDetailsService userDetailsService;
  6. private String secretKey;
  7. @PostConstruct
  8. protected void init() {
  9. secretKey = Base64.getEncoder().encodeToString(jwtProperties.getSecretKey().getBytes());
  10. }
  11. public String createToken(String username, List<String> roles) {
  12. Claims claims = Jwts.claims().setSubject(username);
  13. claims.put("roles", roles);
  14. Date now = new Date();
  15. Date validity = new Date(now.getTime() + jwtProperties.getValidityInMs());
  16. return Jwts.builder()//
  17. .setClaims(claims)//
  18. .setIssuedAt(now)//
  19. .setExpiration(validity)//
  20. .signWith(SignatureAlgorithm.HS256, secretKey)//
  21. .compact();
  22. }
  23. public Authentication getAuthentication(String token) {
  24. UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
  25. return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
  26. }
  27. public String getUsername(String token) {
  28. return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
  29. }
  30. public String resolveToken(HttpServletRequest req) {
  31. String bearerToken = req.getHeader("Authorization");
  32. if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
  33. return bearerToken.substring(7);
  34. }
  35. return null;
  36. }
  37. public boolean validateToken(String token) {
  38. try {
  39. Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
  40. if (claims.getBody().getExpiration().before(new Date())) {
  41. return false;
  42. }
  43. return true;
  44. } catch (JwtException | IllegalArgumentException e) {
  45. throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
  46. }
  47. }
  48. }

工具类还实现了另一个功能:从HTTP请求头中获取JWT

2.1.2 Token处理的Filter

Filter是Security处理的关键,基本上都是通过Filter来拦截请求的。首先从请求头取出JWT,然后校验JWT是否合法,如果合法则取出Authentication保存在SecurityContextHolder里。如果不合法,则做异常处理。

  1. public class JwtTokenAuthenticationFilter extends GenericFilterBean {
  2. private JwtTokenProvider jwtTokenProvider;
  3. public JwtTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
  4. this.jwtTokenProvider = jwtTokenProvider;
  5. }
  6. @Override
  7. public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
  8. throws IOException, ServletException {
  9. HttpServletRequest request = (HttpServletRequest) req;
  10. HttpServletResponse response = (HttpServletResponse) res;
  11. try {
  12. String token = jwtTokenProvider.resolveToken(request);
  13. if (token != null && jwtTokenProvider.validateToken(token)) {
  14. Authentication auth = jwtTokenProvider.getAuthentication(token);
  15. if (auth != null) {
  16. SecurityContextHolder.getContext().setAuthentication(auth);
  17. }
  18. }
  19. } catch (InvalidJwtAuthenticationException e) {
  20. response.setStatus(HttpStatus.UNAUTHORIZED.value());
  21. response.getWriter().write("Invalid token");
  22. response.getWriter().flush();
  23. return;
  24. }
  25. filterChain.doFilter(req, res);
  26. }
  27. }

对于异常处理,使用@ControllerAdvice是不行的,应该这个是Filter,在这里抛的异常还没有到DispatcherServlet,无法处理。所以Filter要自己做异常处理:

  1. catch (InvalidJwtAuthenticationException e) {
  2. response.setStatus(HttpStatus.UNAUTHORIZED.value());
  3. response.getWriter().write("Invalid token");
  4. response.getWriter().flush();
  5. return;
  6. }

最后的return不能省略,因为已经要把输出的内容给Response了,没有必要再往后传递,否则报错

  1. java.lang.IllegalStateException: getWriter() has already been called

2.1.3 JWT属性

JWT需要配置一个密钥来加密,同时还要配置JWT令牌的有效期。

  1. @Configuration
  2. @ConfigurationProperties(prefix = "pkslow.jwt")
  3. public class JwtProperties {
  4. private String secretKey = "pkslow.key";
  5. private long validityInMs = 3600_000;
  6. //getter and setter
  7. }

2.2 Spring Security整合

Spring Security的整个框架还是比较复杂的,简化后大概如下图所示:

Springboot集成Spring Security实现JWT认证的步骤详解

它是通过一连串的Filter来进行安全管理。细节这里先不展开讲。

2.2.1 WebSecurityConfigurerAdapter配置

这个配置也可以理解为是FilterChain的配置,可以不用理解,代码很好懂它做了什么:

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. JwtTokenProvider jwtTokenProvider;
  5. @Bean
  6. @Override
  7. public AuthenticationManager authenticationManagerBean() throws Exception {
  8. return super.authenticationManagerBean();
  9. }
  10. @Bean
  11. public PasswordEncoder passwordEncoder() {
  12. return NoOpPasswordEncoder.getInstance();
  13. }
  14. @Override
  15. protected void configure(HttpSecurity http) throws Exception {
  16. http
  17. .httpBasic().disable()
  18. .csrf().disable()
  19. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  20. .and()
  21. .authorizeRequests()
  22. .antMatchers("/auth/login").permitAll()
  23. .antMatchers(HttpMethod.GET, "/admin").hasRole("ADMIN")
  24. .antMatchers(HttpMethod.GET, "/user").hasRole("USER")
  25. .anyRequest().authenticated()
  26. .and()
  27. .apply(new JwtSecurityConfigurer(jwtTokenProvider));
  28. }
  29. }

这里通过HttpSecurity配置了哪些请求需要什么权限才可以访问。

  • /auth/login用于登陆获取JWT,所以都能访问;
  • /admin只有ADMIN用户才可以访问;
  • /user只有USER用户才可以访问。

而之前实现的Filter则在下面配置使用:

  1. public class JwtSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
  2. private JwtTokenProvider jwtTokenProvider;
  3. public JwtSecurityConfigurer(JwtTokenProvider jwtTokenProvider) {
  4. this.jwtTokenProvider = jwtTokenProvider;
  5. }
  6. @Override
  7. public void configure(HttpSecurity http) throws Exception {
  8. JwtTokenAuthenticationFilter customFilter = new JwtTokenAuthenticationFilter(jwtTokenProvider);
  9. http.exceptionHandling()
  10. .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
  11. .and()
  12. .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
  13. }
  14. }

2.2.2 用户从哪来

通常在Spring Security的世界里,都是通过实现UserDetailsService来获取UserDetails的。

  1. @Component
  2. public class CustomUserDetailsService implements UserDetailsService {
  3. private UserRepository users;
  4. public CustomUserDetailsService(UserRepository users) {
  5. this.users = users;
  6. }
  7. @Override
  8. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  9. return this.users.findByUsername(username)
  10. .orElseThrow(() -> new UsernameNotFoundException("Username: " + username + " not found"));
  11. }
  12. }

对于UserRepository,可以从数据库中读取,或者其它用户管理中心。为了方便,我使用Map放了两个用户:

  1. @Repository
  2. public class UserRepository {
  3. private static final Map<String, User> allUsers = new HashMap<>();
  4. @Autowired
  5. private PasswordEncoder passwordEncoder;
  6. @PostConstruct
  7. protected void init() {
  8. allUsers.put("pkslow", new User("pkslow", passwordEncoder.encode("123456"), Collections.singletonList("ROLE_ADMIN")));
  9. allUsers.put("user", new User("user", passwordEncoder.encode("123456"), Collections.singletonList("ROLE_USER")));
  10. }
  11. public Optional<User> findByUsername(String username) {
  12. return Optional.ofNullable(allUsers.get(username));
  13. }
  14. }

3 测试

完成代码编写后,我们来测试一下:

(1)无JWT访问,失败

  1. curl http://localhost:8080/admin
  2. {"timestamp":"2021-02-06T05:45:06.385+0000","status":403,"error":"Forbidden","message":"Access Denied","path":"/admin"}
  3. $ curl http://localhost:8080/user
  4. {"timestamp":"2021-02-06T05:45:16.438+0000","status":403,"error":"Forbidden","message":"Access Denied","path":"/user"}

(2)admin获取JWT,密码错误则失败,密码正确则成功

  1. $ curl http://localhost:8080/auth/login -X POST -d '{"username":"pkslow","password":"xxxxxx"}' -H 'Content-Type: application/json'
  2. {"timestamp":"2021-02-06T05:47:16.254+0000","status":403,"error":"Forbidden","message":"Access Denied","path":"/auth/login"}
  3. $ curl http://localhost:8080/auth/login -X POST -d '{"username":"pkslow","password":"123456"}' -H 'Content-Type: application/json'
  4. eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwa3Nsb3ciLCJyb2xlcyI6WyJST0xFX0FETUlOIl0sImlhdCI6MTYxMjU5MDYxNCwiZXhwIjoxNjEyNTkxMjE0fQ.d4Gi50aaOsHHqpM0d8Mh1960otnZf7rlE3x6xSfakVo

(3)admin带JWT访问/admin,成功;访问/user失败

  1. $ curl http://localhost:8080/admin -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwa3Nsb3ciLCJyb2xlcyI6WyJST0xFX0FETUlOIl0sImlhdCI6MTYxMjU5MDYxNCwiZXhwIjoxNjEyNTkxMjE0fQ.d4Gi50aaOsHHqpM0d8Mh1960otnZf7rlE3x6xSfakVo'
  2. you are admin
  3. $ curl http://localhost:8080/user -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwa3Nsb3ciLCJyb2xlcyI6WyJST0xFX0FETUlOIl0sImlhdCI6MTYxMjU5MDYxNCwiZXhwIjoxNjEyNTkxMjE0fQ.d4Gi50aaOsHHqpM0d8Mh1960otnZf7rlE3x6xSfakVo'
  4. {"timestamp":"2021-02-06T05:51:23.099+0000","status":403,"error":"Forbidden","message":"Forbidden","path":"/user"}

(4)使用过期的JWT访问,失败

  1. $ curl http://localhost:8080/admin -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwa3Nsb3ciLCJyb2xlcyI6WyJST0xFX0FETUlOIl0sImlhdCI6MTYxMjU5MDQ0OSwiZXhwIjoxNjEyNTkwNTA5fQ.CSaubE4iJcYATbLmbb59aNFU1jNCwDFHUV3zIakPU64'
  2. Invalid token

4 总结

代码请查看:https://github.com/LarryDpk/pkslow-samples

以上就是Springboot集成Spring Security实现JWT认证的步骤详解的详细内容,更多关于Springboot集成Spring Security的资料请关注快网idc其它相关文章!

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 Springboot集成Spring Security实现JWT认证的步骤详解 https://www.kuaiidc.com/108887.html

相关文章

发表评论
暂无评论