前言
在現(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)用程序上進(jìn)行一次身份驗證之后,就可以訪問資源應(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進(jìn)行簽名,使用一個字符串作為密鑰。最后,我們將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)過身份驗證的用戶才可以訪問受保護(hù)的資源。我們將使用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;
}
}
}
在上述代碼中,我們使用了一個示例的受保護(hù)資源路徑"/resource",該路徑只允許經(jīng)過身份驗證的用戶訪問。我們從請求頭中提取Bearer令牌,并使用isValidJWT方法驗證令牌的真實性。如果JWT有效,則返回200響應(yīng)代碼和受保護(hù)的資源;否則返回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用戶是否具有訪問受保護(hù)資源的權(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)用程序,讓我們對它們進(jìn)行測試。首先,我們在認(rèn)證應(yīng)用程序上注冊并登錄,以獲取JWT令牌。然后,我們將使用該令牌訪問資源應(yīng)用程序的受保護(hù)資源,并驗證我們是否可以成功訪問。
# 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)用程序的受保護(hù)資源,并返回了正確的響應(yīng)。文章來源:http://www.zghlxwxcb.cn/news/detail-465328.html
總結(jié)
在本文中,我們使用Spring Boot,Spring Security和JWT實現(xiàn)了一個基本的單點登錄系統(tǒng)。我們介紹了JWT的概念,并演示了如何使用它來驗證用戶身份。我們還創(chuàng)建了一個認(rèn)證應(yīng)用程序和一個資源應(yīng)用程序,以演示如何在多個應(yīng)用程序之間共享用戶身份驗證信息。您可以將此范例用作基礎(chǔ)模板,進(jìn)一步擴(kuò)展它以適應(yīng)自己的應(yīng)用程序需求。文章來源地址http://www.zghlxwxcb.cn/news/detail-465328.html
到了這里,關(guān)于Spring Boot單點登錄實踐的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!