SpringBoot實現(xiàn)單點登錄的實現(xiàn)詳解
前言
在現(xiàn)代的Web應(yīng)用程序中,單點登錄(Single Sign-On)已經(jīng)變得越來越流行。單點登錄使得用戶只需要一次認(rèn)證即可訪問多個應(yīng)用程序,同時也提高了應(yīng)用程序的安全性。Spring Boot作為一種廣泛使用的Web開發(fā)框架,在單點登錄方面也提供了很好的支持。
在本文中,我們將使用Spring Boot構(gòu)建一個基本的單點登錄系統(tǒng)。我們將介紹如何使用Spring Security和JSON Web Tokens(JWTs)來實現(xiàn)單點登錄功能。本文假設(shè)您已經(jīng)熟悉Spring Boot和Spring Security。
什么是JWT
在介紹實現(xiàn)單點登錄之前,讓我們先了解一下JWT。JWT是一種基于JSON格式的開放標(biāo)準(zhǔn)(RFC 7519),用于在不同的應(yīng)用程序之間安全地傳輸信息。它由三個部分組成:
- 標(biāo)頭(Header):包含JWT的類型和使用的簽名算法。
- 負(fù)載(Payload):包含實際的信息。
- 簽名(Signature):使用私鑰生成的簽名,用于驗證JWT的真實性。
JWT通常在身份驗證過程中使用,以便在不需要存儲用戶信息的情況下驗證用戶身份。由于JWT是基于標(biāo)準(zhǔn)化的JSON格式構(gòu)建的,因此在多種編程語言中都可以輕松地實現(xiàn)和解析。
實現(xiàn)單點登錄
下面我們來介紹如何使用JWT實現(xiàn)基本的單點登錄系統(tǒng)。這個系統(tǒng)由兩個應(yīng)用程序組成:認(rèn)證應(yīng)用程序和資源應(yīng)用程序。用戶在認(rèn)證應(yīng)用程序上進行一次身份驗證之后,就可以訪問資源應(yīng)用程序。
認(rèn)證應(yīng)用程序
我們首先需要構(gòu)建一個認(rèn)證應(yīng)用程序,用于認(rèn)證用戶信息并生成JWT。
添加依賴
首先,我們需要添加以下依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
- spring-boot-starter-security:用于提供基本的安全性支持。
- jjwt:JSON Web Token的Java實現(xiàn)。
- spring-boot-starter-web:用于提供Web應(yīng)用程序支持。
配置Spring Security
接下來,我們需要配置Spring Security。我們將使用Spring Security的默認(rèn)配置,并添加一個自定義的UserDetailsService來從數(shù)據(jù)庫中加載用戶信息。
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/auth/**").permitAll() .anyRequest().authenticated() .and().formLogin() .loginPage("/auth/login") .successHandler(authenticationSuccessHandler()) .failureHandler(authenticationFailureHandler()) .permitAll() .and().logout() .logoutUrl("/auth/logout") .logoutSuccessUrl("/auth/login?logout") .invalidateHttpSession(true) .deleteCookies("JSESSIONID"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationSuccessHandler authenticationSuccessHandler() { return new JWTAuthenticationSuccessHandler(); } @Bean public AuthenticationFailureHandler authenticationFailureHandler() { return new JWTAuthenticationFailureHandler(); } }
在上述配置中,我們定義了一個路由表達(dá)式"/auth/**"允許匿名訪問,這意味著認(rèn)證應(yīng)用程序的登錄和注冊頁面可以被未經(jīng)身份驗證的用戶訪問。我們還定義了自定義的AuthenticationSuccessHandler和AuthenticationFailureHandler,用于在用戶身份驗證成功或失敗時生成JWT并將其返回給用戶。這些處理程序?qū)⒃谙乱徊街袑崿F(xiàn)。
實現(xiàn)自定義的AuthenticationSuccessHandler和AuthenticationFailureHandler
在上述配置中,我們使用了自定義的AuthenticationSuccessHandler和AuthenticationFailureHandler。讓我們來實現(xiàn)它們。
public class JWTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private static final String JWT_SECRET = "secret"; private static final long JWT_EXPIRATION_TIME = 864000000; // 10 days @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String username = authentication.getName(); String token = Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, JWT_SECRET.getBytes()) .compact(); response.setHeader("Authorization", "Bearer " + token); response.getWriter().write("{\"token\":\"Bearer " + token + "\"}"); response.setContentType("application/json"); } }
在上述代碼中,我們使用了JJWT庫來生成JWT。在onAuthenticationSuccess方法中,我們首先從Authentication對象獲取用戶名,然后使用用戶名創(chuàng)建JWT。我們設(shè)置JWT的有效期為10天,并使用HS512簽名算法對JWT進行簽名,使用一個字符串作為密鑰。最后,我們將JWT作為Bearer令牌添加到響應(yīng)消息頭中,并封裝在JSON格式的響應(yīng)體中返回給客戶端。
public class JWTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("{\"error\":\"Bad credentials\"}"); response.setContentType("application/json"); } }
在上述代碼中,我們將響應(yīng)代碼設(shè)置為401(未經(jīng)授權(quán)),并向響應(yīng)體中添加一個錯誤消息,以通知客戶端身份驗證失敗。
實現(xiàn)授權(quán)控制器
現(xiàn)在我們已經(jīng)創(chuàng)建了認(rèn)證應(yīng)用程序的基本安全性,讓我們來構(gòu)建資源應(yīng)用程序并實現(xiàn)授權(quán)控制器,以確保只有經(jīng)過身份驗證的用戶才可以訪問受保護的資源。我們將使用JWT來驗證用戶身份。
@RestController public class ResourceController { private static final String JWT_SECRET = "secret"; @GetMapping("/resource") public ResponseEntity<String> getResource(HttpServletRequest request) { String token = request.getHeader("Authorization").replace("Bearer ", ""); if (isValidJWT(token)) { return ResponseEntity.ok("Protected resource"); } else { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } private boolean isValidJWT(String jwt) { try { Jwts.parser().setSigningKey(JWT_SECRET.getBytes()).parseClaimsJws(jwt); return true; } catch (JwtException e) { return false; } } }
在上述代碼中,我們使用了一個示例的受保護資源路徑"/resource",該路徑只允許經(jīng)過身份驗證的用戶訪問。我們從請求頭中提取Bearer令牌,并使用isValidJWT方法驗證令牌的真實性。如果JWT有效,則返回200響應(yīng)代碼和受保護的資源;否則返回401(未經(jīng)授權(quán))響應(yīng)代碼。
資源應(yīng)用程序
現(xiàn)在我們已經(jīng)創(chuàng)建了認(rèn)證應(yīng)用程序,讓我們來創(chuàng)建一個資源應(yīng)用程序,以便用戶可以在驗證后訪問它。資源應(yīng)用程序?qū)Ⅱ炞C用戶是否具有訪問受保護資源的權(quán)限。
配置Spring Security
我們首先需要配置Spring Security,以使資源應(yīng)用程序能夠驗證JWT并授予用戶訪問權(quán)限。
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/resource").authenticated() .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().addFilterBefore(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); } }
在上述配置中,我們定義了一個路由表達(dá)式"/resource",只有經(jīng)過身份驗證的用戶才能訪問。我們還將會話管理策略設(shè)置為STATELESS,以避免使用HTTP會話。
實現(xiàn)JWTAuthorizationFilter
接下來,我們需要實現(xiàn)JWTAuthorizationFilter,以驗證來自客戶端的JWT并將其與用戶信息相關(guān)聯(lián)。這將允許我們檢查用戶是否具有訪問資源的權(quán)限。
public class JWTAuthorizationFilter extends OncePerRequestFilter { private static final String JWT_SECRET = "secret"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authorizationHeader = request.getHeader("Authorization"); if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } String jwt = authorizationHeader.replace("Bearer ", ""); try { Jws<Claims> claimsJws = Jwts.parser().setSigningKey(JWT_SECRET.getBytes()).parseClaimsJws(jwt); String username = claimsJws.getBody().getSubject(); List<GrantedAuthority> authorities = new ArrayList<>(); UserDetails userDetails = new User(username, "", authorities); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (JwtException e) { throw new ServletException("Invalid JWT"); } filterChain.doFilter(request, response); } }
在上述代碼中,我們查詢Authorization頭以查找Bearer令牌。如果令牌不存在或不正確,則請求將繼續(xù)傳遞。否則,我們使用JJWT庫驗證JWT的真實性,并獲取用戶的用戶名。我們將用戶名創(chuàng)建為Spring Security的UserDetails對象并創(chuàng)建一個UsernamePasswordAuthenticationToken,用于將身份驗證信息設(shè)置為當(dāng)前Spring Security上下文的一部分。完成后,請求將繼續(xù)傳遞并授權(quán)用戶訪問資源。
測試
現(xiàn)在我們已經(jīng)創(chuàng)建了認(rèn)證應(yīng)用程序和資源應(yīng)用程序,讓我們對它們進行測試。首先,我們在認(rèn)證應(yīng)用程序上注冊并登錄,以獲取JWT令牌。然后,我們將使用該令牌訪問資源應(yīng)用程序的受保護資源,并驗證我們是否可以成功訪問。
# Register and login to authentication application
$ curl -s -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"password"}' http://localhost:8080/auth/signup
$ curl -s -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"password"}' http://localhost:8080/auth/login
# Get JWT token
$ TOKEN=$(curl -si -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"password"}' http://localhost:8080/auth/login | grep 'Authorization:' | awk '{print $2}')
# Access protected resource in resource application
$ curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8090/resource
Protected resource
通過測試,我們可以看到我們成功地訪問了資源應(yīng)用程序的受保護資源,并返回了正確的響應(yīng)。
總結(jié)
在本文中,我們使用Spring Boot,Spring Security和JWT實現(xiàn)了一個基本的單點登錄系統(tǒng)。我們介紹了JWT的概念,并演示了如何使用它來驗證用戶身份。我們還創(chuàng)建了一個認(rèn)證應(yīng)用程序和一個資源應(yīng)用程序,以演示如何在多個應(yīng)用程序之間共享用戶身份驗證信息。您可以將此范例用作基礎(chǔ)模板,進一步擴展它以適應(yīng)自己的應(yīng)用程序需求。
以上就是SpringBoot實現(xiàn)單點登錄的實現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot單點登錄的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot使用注解實現(xiàn)鑒權(quán)功能
這篇文章主要介紹了springboot使用注解實現(xiàn)鑒權(quán)功能,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-12-12SpringBoot整合flyway實現(xiàn)步驟解析
這篇文章主要介紹了SpringBoot整合flyway實現(xiàn)步驟解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08MyBatis實現(xiàn)批量插入數(shù)據(jù),多重forEach循環(huán)
這篇文章主要介紹了MyBatis實現(xiàn)批量插入數(shù)據(jù),多重forEach循環(huán)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02