SpringBoot3 整合SpringSecurity
Maven
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
JwtFilter
package com.system.security; import cn.hutool.core.util.StrUtil; import com.auth0.jwt.interfaces.DecodedJWT; import com.system.common.exception.ServerException; import com.system.common.utlis.jwt.JwtUtils; import com.system.common.utlis.result.Prefix; import jakarta.annotation.Resource; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; /** * @author kuaiting */ @Component public class JwtFilter extends OncePerRequestFilter { @Resource private JwtUtils jwtUtils; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 實現過濾邏輯 String authorization = request.getHeader(Prefix.TOKEN_KEY); System.out.println(authorization); if (authorization != null && authorization.startsWith(Prefix.TOKEN_BEARER)) { // 驗證JWT並設定使用者資訊到SecurityContextHolder中 DecodedJWT jwt = jwtUtils.resolveToken(authorization); if (jwt != null) { Long uid = jwtUtils.getUid(jwt); SecurityUser details = jwtUtils.getUserDetails(jwt); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); request.setAttribute("uid", uid); } } filterChain.doFilter(request, response); } public void validate(HttpServletRequest request) throws ServerException { //請求中傳來的驗證碼 String code = request.getParameter("code"); String sessionCode = request.getSession().getAttribute("session_code").toString(); if (StrUtil.isEmpty(code)) { throw new ServerException("驗證碼不能為空!"); } if (StrUtil.isEmpty(sessionCode)) { throw new ServerException("驗證碼已經失效!"); } if (!sessionCode.equalsIgnoreCase(code)) { throw new ServerException("驗證碼輸入錯誤!"); } } }
SecurityConfig
package com.system.security; import com.system.common.utlis.jwt.JwtUtils; import com.system.common.utlis.result.Prefix; import com.system.common.utlis.result.ResData; import com.system.common.utlis.result.ResEnum; import com.system.system.dao.RoleDao; import com.system.system.entity.vo.AuthVO; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; 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; import java.io.IOException; import java.io.PrintWriter; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author kuaiting */ @Configuration public class SecurityConfig { @Resource JwtUtils jwtUtils; @Resource private JwtFilter jwtFilter; @Resource private RoleDao roleDao; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } private final String[] paths = { "/druid/**", "/system/captcha/line", "/druid/login.html/**", "/system/login", "/js/**", "/*/*.json", "/*/*.yml", "/prims/**", "/type/**", "/system/file/**", "/diagram-viewer/**", "/images/**", "/api/login/**", "/api/file/**", "/css/**", "/*/*.ico", "/swagger-resources/**", "/swagger/**", "/swagger-ui/**", "/webjars/**", "/v3/**", "/v2/**", "/doc.html/**" }; @Bean public SecurityFilterChain securityChain(HttpSecurity http) throws Exception { return http.authorizeHttpRequests(conf -> conf.requestMatchers(paths).permitAll() .anyRequest().authenticated()) .formLogin(conf -> conf.loginProcessingUrl("/system/login") .usernameParameter("username") .passwordParameter("password") .successHandler(this::onAuthenticationSuccess) .failureHandler(this::onAuthenticationFailure)) .exceptionHandling(conf -> conf.authenticationEntryPoint(this::noLogin) .accessDeniedHandler(this::noPermission)) .logout(conf -> conf.logoutUrl("/system/logout") .logoutSuccessHandler(this::onLogoutSuccess)) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .build(); } private void noPermission(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter writer = response.getWriter(); jsonWriter(writer, ResEnum.FORBIDDEN.getCode(), ResEnum.FORBIDDEN.getMsg()); } public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { SecurityUser user = (SecurityUser) authentication.getPrincipal(); Long uid = user.getId(); Set<String> permissions = new HashSet<>(); for (GrantedAuthority authority : user.getAuthorities()) { String auth = authority.getAuthority(); permissions.add(auth); } String token = jwtUtils.createToken(user, uid, user.getUsername(),user.getPassword()); AuthVO authVo = new AuthVO(); authVo.setRole(roleDao.getUserRoles(uid)); authVo.setPermission(permissions); authVo.setKey(Prefix.TOKEN_KEY); authVo.setToken(token); authVo.setExpire(jwtUtils.expireTime().getTime()); response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_OK); PrintWriter writer = response.getWriter(); jsonWriter(writer, ResEnum.SUCCESS.getCode(), ResEnum.SUCCESS.getMsg(), authVo); } public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_OK); PrintWriter writer = response.getWriter(); jsonWriter(writer, ResEnum.UNAUTHORIZED.getCode(), exception.getMessage()); } public void noLogin(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_OK); PrintWriter writer = response.getWriter(); jsonWriter(writer, ResEnum.UNAUTHORIZED.getCode(), ResEnum.UNAUTHORIZED.getMsg()); } public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String authorization = request.getHeader("Authorization"); response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_OK); PrintWriter writer = response.getWriter(); // 登出時,刪除redis中的token if (jwtUtils.invalidateToken(authorization)) { jsonWriter(writer, ResEnum.SUCCESS.getCode(), ResEnum.SUCCESS.getMsg()); } else { jsonWriter(writer, ResEnum.FAIL.getCode(), ResEnum.FAIL.getMsg()); } } private void jsonWriter(PrintWriter writer, Integer code, String message) { jsonWriter(writer, code, message, null); } private void jsonWriter(PrintWriter writer, Integer code, String message, Object data) { writer.write(ResData.asJson(code, message, data)); writer.flush(); writer.close(); } private List<Long> getRoleIds(Long uid) { return roleDao.getRoleIds(uid); } private List<String> getUserRoles(Long uid) { return roleDao.getUserRoles(uid); } private Set<String> getPermissions(List<Long> roleIds) { Set<String> all = new HashSet<>(); for (Long id : roleIds) { List<String> permissions = getUserPermissions(id); all.addAll(permissions); } return all; } private List<String> getUserPermissions(Long rid) { return roleDao.getPermissions(rid); } }
User
package com.system.security; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; /** * @author kuaiting */ @Data @NoArgsConstructor public class SecurityUser implements UserDetails, Serializable { private Long id; private String username; private String password; private Integer status; private Set<String> permissions; private Collection<? extends GrantedAuthority> authorities; public SecurityUser( Long id,String username, String password, Integer status, Set<String> permissions) { this.id = id; this.username = username; this.password = password; this.status = status; this.permissions = permissions; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { if (authorities != null) { return authorities; } //把permissions中字串型別的許可權資訊轉換成GrantedAuthority物件存入authorities中 authorities = permissions.stream(). map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return status == 0; } }
JwtUtils
package com.system.common.utlis.jwt; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.system.common.utlis.redis.RedisService; import com.system.common.utlis.result.Prefix; import com.system.security.SecurityUser; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.*; import java.util.stream.Collectors; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expire}") private Integer expire; @Resource private RedisService redisService; public Boolean invalidateToken(String haeadToken) { String token = convertToken(haeadToken); if (token == null) { return false; } try { DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret)).build().verify(token); return deleteToken(jwt.getId(), jwt.getExpiresAt()); } catch (JWTVerificationException e) { return null; } } // 刪除redis 中的Token private Boolean deleteToken(String id, Date date) { if (isInvalidToken(id)) { return false; } Date now = new Date(); long expire = Math.max(date.getTime() - now.getTime(), 0); redisService.set(Prefix.JWT_BLACK_LIST + id, id, expire); return true; } // 驗證 redis 中 token 是否存在 private Boolean isInvalidToken(String id) { return Boolean.TRUE.equals(redisService.hasKey(Prefix.JWT_BLACK_LIST + id)); } public String createToken(UserDetails details, Long id, String username,String password) { Algorithm algorithm = Algorithm.HMAC256(secret); redisService.set(Prefix.JWT_BLACK_LIST + id, id, expire); return JWT.create().withJWTId(String.valueOf(id)) .withClaim("id", id) .withClaim("username", username) .withClaim("password", password) .withClaim("authorities", getAuths(details)) .withExpiresAt(expireTime()) .sign(algorithm); } public String getAuths(UserDetails details) { return details.getAuthorities().stream().map(GrantedAuthority::getAuthority). collect(Collectors.joining(",")); } // 獲取過期時間 public Date expireTime() { // 過期時間 Calendar instance = Calendar.getInstance(); instance.add(Calendar.HOUR, expire * 24); // 預設7天 return instance.getTime(); } // 解析 JWT token public DecodedJWT resolveToken(String haeadToken) { String token = convertToken(haeadToken); if (token == null) { return null; } try { DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret)).build().verify(token); if (isInvalidToken(jwt.getId())) { return null; } Date expires = jwt.getExpiresAt(); return new Date().after(expires) ? null : jwt; } catch (JWTVerificationException e) { return null; } } // 解析 擷取真正有用的token private String convertToken(String haeadToken) { if (haeadToken == null || !haeadToken.startsWith(Prefix.TOKEN_BEARER)) { return null; } return haeadToken.substring(7); } // 獲取使用者資訊 public SecurityUser getUserDetails(DecodedJWT jwt) { Map<String, Claim> claims = jwt.getClaims(); String authorities = claims.get("authorities").asString(); Set<SimpleGrantedAuthority> permissions = new HashSet<>(); for (String auth : authorities.split(",")) { permissions.add(new SimpleGrantedAuthority(auth)); } SecurityUser sysUser = new SecurityUser(); sysUser.setId(jwt.getClaim("id").asLong()); sysUser.setUsername(claims.get("username").toString()); sysUser.setPassword(claims.get("password").toString()); sysUser.setStatus(0); sysUser.setAuthorities(permissions); return sysUser; } // 獲取使用者ID public Long getUid(DecodedJWT jwt) { return jwt.getClaim("id").asLong(); } }
轉:https://blog.csdn.net/qq_45834006/article/details/136403939
https://docs.spring.io/spring-security/reference/index.html