电竞比分网-中国电竞赛事及体育赛事平台

分享

SpringBoot 整合 Spring Security 實(shí)現(xiàn)安全認(rèn)證【SpringBoot系列9】

 程序員讀書空間 2023-03-30 發(fā)布于浙江

1 項(xiàng)目準(zhǔn)備

SpringBoot 搭建項(xiàng)目 【SpringBoot系列1】

SpringBoot 集成Redis緩存 以及實(shí)現(xiàn)基本的數(shù)據(jù)緩存【SpringBoot系列8】本文章在此基礎(chǔ)上進(jìn)行搭建。

Spring Security 是 Spring 社區(qū)的一個(gè)頂級(jí)項(xiàng)目,也是 Spring Boot 官方推薦使用的安全框架。官網(wǎng)在這里:

https://spring.io/projects/spring-security

本文章實(shí)現(xiàn)的是SpringBoot整合Spring Security實(shí)現(xiàn)認(rèn)證校驗(yàn)功能,實(shí)現(xiàn)方式有多種,本文章只是其中一種,如有不足,歡迎留言。

首先在pom.xml 添加依賴如下:

 <dependency>       <groupId>com.alibaba</groupId>       <artifactId>fastjson</artifactId>       <version>2.0.25</version>   </dependency>
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.0.4</version> </dependency>

JWT(JSON Web Token) 用來生成 Token ,JWT實(shí)際上就是一個(gè)字符串,它由三部分組成,頭部、載荷與簽名。

添加依賴后,啟動(dòng)項(xiàng)目,在瀏覽器中訪問任何一個(gè)接口都會(huì)出現(xiàn) 登錄認(rèn)證

1 jjwt 生成 token 工具

這里就是根據(jù) 用戶的 username + 密鑰來生成token ,然后解密 token 等等,在 Spring Security 認(rèn)證過程中使用。

import io.jsonwebtoken.*;import io.jsonwebtoken.io.Decoders;import io.jsonwebtoken.security.Keys;import lombok.extern.slf4j.Slf4j;import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;import java.util.Date;
@Component@Slf4jpublic class JWTGenerator { //密鑰 private static String sign ="cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ="; //生成token public String generateToken(Authentication authentication) { //用戶的核心標(biāo)識(shí) String username = authentication.getName(); // 過期時(shí)間 - 30分鐘 Date expireDate = new Date(System.currentTimeMillis() + 30 * 60 * 1000); String token = Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(expireDate) .signWith(generalKeyByDecoders()) //設(shè)置token加密方式和密 .compact(); return token; }
public static SecretKey generalKeyByDecoders() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(sign)); }
/** * 解密token * @param token * @return */ public String getUsernameFromJWT(String token) { JwtParserBuilder builder = Jwts.parserBuilder(); Jws<Claims> claimsJws = builder .setSigningKey(generalKeyByDecoders()) .build() .parseClaimsJws(token); return claimsJws.getBody().getSubject(); }
/** * 校驗(yàn)token * @param token * @return */ public boolean validateToken(String token) { log.error("驗(yàn)證 token {}", token); try { JwtParserBuilder builder = Jwts.parserBuilder();
Jws<Claims> claimsJws = builder .setSigningKey(generalKeyByDecoders()) .build() .parseClaimsJws(token); return true; } catch (ExpiredJwtException e) { Claims claims = e.getClaims(); // 檢查token throw new BadCredentialsException("TOKEN已過期,請(qǐng)重新登錄!"); } catch (AuthenticationException e) { throw new AuthenticationCredentialsNotFoundException("JWT was expired or incorrect"); } catch (Exception ex) { log.error("token認(rèn)證失敗 {}", ex.getMessage()); throw new AuthenticationCredentialsNotFoundException("JWT was expired or incorrect"); } }}

2 登錄認(rèn)證 Controller 定義

@Slf4j@RestController@RequestMapping("/api/auth")public class AuthController {
@Autowired private AuthenticationManager authenticationManager; @Autowired private JWTGenerator jwtGenerator;
@PostMapping("login") public R login(@RequestBody LoginRequest loginDto){ log.info("登錄認(rèn)證開始 {}",loginDto.toString()); Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginDto.getUserName(), loginDto.getPassword())); // 認(rèn)證成功存儲(chǔ)認(rèn)證信息到上下文 SecurityContextHolder.getContext().setAuthentication(authentication); log.info("登錄認(rèn)證完成 {}",loginDto.toString()); String token = jwtGenerator.generateToken(authentication); log.info("登錄認(rèn)證生成 token {}",token); return R.okData(token); } }

JWTGenerator 第一步中定義的 token 生成工具,在登錄校驗(yàn)完成時(shí),生成token

AuthenticationManager 只關(guān)注認(rèn)證成功與否而并不關(guān)心具體的認(rèn)證方式,如果驗(yàn)證成功,則返回完全填充的Authentication對(duì)象(包括授予的權(quán)限)。

import lombok.Data;import lombok.ToString;
import java.io.Serializable;@Data@ToStringpublic class LoginRequest implements Serializable { private String userName ; private String password;}

3 核心配置 SecurityConfig

SecurityConfig 用來配置 Spring Security 的攔截策略以及認(rèn)證策略等等

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration@EnableWebSecuritypublic class SecurityConfig {
//自定義異常認(rèn)證處理 private JwtAuthEntryPoint authEntryPoint; //自定義授權(quán)異常處理 private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired public SecurityConfig(JwtAuthEntryPoint authEntryPoint, MyAccessDeniedHandler myAccessDeniedHandler) { this.authEntryPoint = authEntryPoint; this.myAccessDeniedHandler = myAccessDeniedHandler; }
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler) .authenticationEntryPoint(authEntryPoint) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and() .authorizeRequests() //放行靜態(tài)資源文件夾(路徑要具體情況具體分析) .antMatchers( "/api/auth/**", "/css/**", "/js/**", "/image/**", "/app/**", "/swagger/**", "/swagger-ui.html", "/app/**", "/swagger-resources/**", "/v2/**", "/webjars/**").permitAll()
.anyRequest().authenticated() .and() .httpBasic(); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); }
@Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }
@Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
// 自定義 認(rèn)證過濾器 @Bean public JWTAuthenticationFilter jwtAuthenticationFilter() { return new JWTAuthenticationFilter(); }}
3.1 JwtAuthEntryPoint 自定義的認(rèn)證失敗的回調(diào)處理
import com.alibaba.fastjson.JSONObject;import lombok.extern.slf4j.Slf4j;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.stereotype.Component;
import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.HashMap;import java.util.Map;
@Component@Slf4jpublic class JwtAuthEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
String message = authException.getMessage(); log.error("token 攔截 {}",message); response.setCharacterEncoding("utf-8"); response.setContentType("text/javascript;charset=utf-8"); Map<String,Object> map = new HashMap<>(); map.put("code",403); map.put("message","您未登錄,沒有訪問權(quán)限"); response.getWriter().print(JSONObject.toJSONString(map)); }}
3.2 MyAccessDeniedHandler 自定義的授權(quán)失敗的回調(diào)處理
import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;
import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
/** * 授權(quán)異常 */@Componentpublic class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException { response.setStatus(403); response.getWriter().write("Forbidden:" + accessDeniedException.getMessage()); }}

4 核心過濾器 JWTAuthenticationFilter

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.util.StringUtils;import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
public class JWTAuthenticationFilter extends OncePerRequestFilter {
@Autowired private JWTGenerator tokenGenerator; @Autowired private CustomUserDetailsService customUserDetailsService;

@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //獲取請(qǐng)求頭中的 token 信息 String token = getJWTFromRequest(request); //校驗(yàn)token if(StringUtils.hasText(token) && tokenGenerator.validateToken(token)) { //解析 token 中的用戶信息 (用戶的唯一標(biāo)識(shí) ) String username = tokenGenerator.getUsernameFromJWT(token);
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } filterChain.doFilter(request, response); }
/** * 就是校驗(yàn)請(qǐng)求頭的一種格式 可以隨便定義 * 只要可以解析 就可以 * @param request * @return */ private String getJWTFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7, bearerToken.length()); } return null;    }

5 CustomUserDetailsService 用戶校驗(yàn)實(shí)現(xiàn)

@Servicepublic class CustomUserDetailsService  implements UserDetailsService {
private UserService userService;
@Autowired public CustomUserDetailsService(UserService userService) { this.userService = userService; }
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserInfo user = userService.getByUsername(username); if(user==null){ throw new UsernameNotFoundException("Username not found"); } User user1 = new User(user.getUserName(), user.getPassword(), mapRolesToAuthorities(user.getRoles())); return user1; }
private Collection<GrantedAuthority> mapRolesToAuthorities(List<Role> roles) { return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()); }}

這里使用到的 UserService 就是項(xiàng)目中查詢用戶信息的服務(wù)。

然后使用 postman 來訪問接口

然后調(diào)用 登錄接口生成 token

然后在訪問其他接口里放入請(qǐng)求頭信息

項(xiàng)目源碼在這里 

https://gitee.com/android.long/spring-boot-study/tree/master/biglead-api-07-security

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多