激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Springboot集成Spring Security實現JWT認證的步驟詳解

Springboot集成Spring Security實現JWT認證的步驟詳解

2021-08-05 11:06南瓜慢說 Java教程

這篇文章主要介紹了Springboot集成Spring Security實現JWT認證的步驟詳解,幫助大家更好的理解和使用springboot,感興趣的朋友可以了解下

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 Security和JWT加入到項目的依賴中去:

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

工具類還實現了另一個功能:從HTTP請求頭中獲取JWT。

2.1.2 Token處理的Filter

Filter是Security處理的關鍵,基本上都是通過Filter來攔截請求的。首先從請求頭取出JWT,然后校驗JWT是否合法,如果合法則取出Authentication保存在SecurityContextHolder里。如果不合法,則做異常處理。

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

對于異常處理,使用@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.  
  4. @Autowired
  5. JwtTokenProvider jwtTokenProvider;
  6.  
  7. @Bean
  8. @Override
  9. public AuthenticationManager authenticationManagerBean() throws Exception {
  10. return super.authenticationManagerBean();
  11. }
  12.  
  13. @Bean
  14. public PasswordEncoder passwordEncoder() {
  15. return NoOpPasswordEncoder.getInstance();
  16. }
  17.  
  18. @Override
  19. protected void configure(HttpSecurity http) throws Exception {
  20. http
  21. .httpBasic().disable()
  22. .csrf().disable()
  23. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  24. .and()
  25. .authorizeRequests()
  26. .antMatchers("/auth/login").permitAll()
  27. .antMatchers(HttpMethod.GET, "/admin").hasRole("ADMIN")
  28. .antMatchers(HttpMethod.GET, "/user").hasRole("USER")
  29. .anyRequest().authenticated()
  30. .and()
  31. .apply(new JwtSecurityConfigurer(jwtTokenProvider));
  32. }
  33. }

這里通過HttpSecurity配置了哪些請求需要什么權限才可以訪問。

  • /auth/login用于登陸獲取JWT,所以都能訪問;
  • /admin只有ADMIN用戶才可以訪問;
  • /user只有USER用戶才可以訪問。

而之前實現的Filter則在下面配置使用:

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

2.2.2 用戶從哪來

通常在Spring Security的世界里,都是通過實現UserDetailsService來獲取UserDetails的。

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

對于UserRepository,可以從數據庫中讀取,或者其它用戶管理中心。為了方便,我使用Map放了兩個用戶:

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

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.  
  4. $ curl http://localhost:8080/user
  5. {"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.  
  4. $ curl http://localhost:8080/auth/login -X POST -d '{"username":"pkslow","password":"123456"}' -H 'Content-Type: application/json'
  5. 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.  
  4. $ curl http://localhost:8080/user -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwa3Nsb3ciLCJyb2xlcyI6WyJST0xFX0FETUlOIl0sImlhdCI6MTYxMjU5MDYxNCwiZXhwIjoxNjEyNTkxMjE0fQ.d4Gi50aaOsHHqpM0d8Mh1960otnZf7rlE3x6xSfakVo'
  5. {"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的資料請關注服務器之家其它相關文章!

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 中文字幕www. | 久久久aa | 成人国产精品一区 | 羞羞视频在线免费 | 国产资源在线播放 | 男人久久天堂 | 免费一级毛片观看 | 国产69精品福利视频 | 国产一级淫片在线观看 | 欧美综合在线观看 | 成人毛片视频免费看 | 亚洲国产精品久久久久久久久久 | 欧美一级欧美 | 黄色网址进入 | 九九精品免费 | 国产精品久久久久一区二区 | 国产精品99久久久久久宅女 | 第四色成人网 | 精品国产91久久久久久久妲己 | 《97色伦在色在线播放》 | 成人毛片100部 | 亚洲福利视| 精品国产一区二区三区四区在线 | 91 在线免费观看 | 久久久精品视频国产 | 国产日韩一区二区三区在线观看 | 久久草在线视频国产 | 黄色网址免费入口 | 日本精品一区二区 | 性大片免费看 | 亚洲第一视频在线 | 免费国产在线视频 | 精品一区二区在线播放 | 国产精品视频一区二区三区四 | 成片免费观看大全 | 国产免费片 | 亚洲精品午夜国产va久久成人 | 性大片性大片免费 | av视在线| 鲁丝一区二区三区不属 | 欧美一级淫片a免费播放口 91九色蝌蚪国产 |