SpringSecurity依赖
<!--SpringSecurity起步依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
jwt依赖
<!--jwt令牌-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
SecurityConfig配置类
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;/*** @Description: SpringSecurity配置类* @Author: 翰戈.summer* @Date: 2023/11/17* @Param:* @Return:*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {private final UserDetailsService userDetailsService;/*** 加载用户信息*/@Beanpublic UserDetailsService userDetailsService() {return userDetailsService;}/*** 密码编码器*/@Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 身份验证管理器*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {return configuration.getAuthenticationManager();}/*** 处理身份验证*/@Beanpublic AuthenticationProvider authenticationProvider() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());daoAuthenticationProvider.setUserDetailsService(userDetailsService);return daoAuthenticationProvider;}/*** @Description: 配置SecurityFilterChain过滤器链* @Author: 翰戈.summer* @Date: 2023/11/17* @Param: HttpSecurity* @Return: SecurityFilterChain*/@Beanpublic SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.requestMatchers(HttpMethod.POST, "/api/user/login").permitAll() //登录放行.anyRequest().authenticated());httpSecurity.authenticationProvider(authenticationProvider());//禁用登录页面httpSecurity.formLogin(AbstractHttpConfigurer::disable);//禁用登出页面httpSecurity.logout(AbstractHttpConfigurer::disable);//禁用sessionhttpSecurity.sessionManagement(AbstractHttpConfigurer::disable);//禁用httpBasichttpSecurity.httpBasic(AbstractHttpConfigurer::disable);//禁用csrf保护httpSecurity.csrf(AbstractHttpConfigurer::disable);return httpSecurity.build();}
}
UserDetailsService
import com.demo.mapper.AuthorityMapper;
import com.demo.mapper.UserMapper;
import com.demo.pojo.AuthorityEntity;
import com.demo.pojo.UserEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.StringJoiner;/*** @Description: 用户登录* @Author: 翰戈.summer* @Date: 2023/11/16* @Param:* @Return:*/
@Service
@RequiredArgsConstructor
public class UserLoginDetailsServiceImpl implements UserDetailsService {private final UserMapper userMapper;private final AuthorityMapper authorityMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserEntity userEntity = userMapper.selectUserByUsername(username);List<AuthorityEntity> authorities = authorityMapper.selectAuthorityByUsername(username);StringJoiner stringJoiner = new StringJoiner(",", "", "");authorities.forEach(authority -> stringJoiner.add(authority.getAuthorityName()));return new User(userEntity.getUsername(), userEntity.getPassword(),AuthorityUtils.commaSeparatedStringToAuthorityList(stringJoiner.toString()));}
}
UserDetails
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;/*** @Description: SpringSecurity用户实体类* @Author: 翰戈.summer* @Date: 2023/11/18* @Param:* @Return:*/
@NoArgsConstructor
@AllArgsConstructor
public class UserDetailsEntity implements UserDetails {private String username;private String password;private Collection<? extends GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}@Overridepublic String toString() {return "UserDetailsEntity{" +"username='" + username + '\'' +", password='" + password + '\'' +", authorities=" + authorities +'}';}
}
jwt工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.Map;/*** @Description: 生成和解析jwt令牌* @Author: 翰戈.summer* @Date: 2023/11/16* @Param:* @Return:*/
@Component
@RequiredArgsConstructor
public class JwtUtils {private final JwtProperties jwtProperties;/*** @Description: 生成令牌* @Author: 翰戈.summer* @Date: 2023/11/16* @Param: Map* @Return: String jwt*/public String getJwt(Map<String, Object> claims) {String signingKey = jwtProperties.getSigningKey();Long expire = jwtProperties.getExpire();return Jwts.builder().setClaims(claims) //设置载荷内容.signWith(SignatureAlgorithm.HS256, signingKey) //设置签名算法.setExpiration(new Date(System.currentTimeMillis() + expire)) //设置有效时间.compact();}/*** @Description: 解析令牌* @Author: 翰戈.summer* @Date: 2023/11/16* @Param: String jwt* @Return: Claims claims*/public Claims parseJwt(String jwt) {String signingKey = jwtProperties.getSigningKey();return Jwts.parser().setSigningKey(signingKey) //指定签名密钥.parseClaimsJws(jwt) //开始解析令牌.getBody();}
}
UserLoginController
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;/*** @Description: 用户登录操作相关接口* @Author: 翰戈.summer* @Date: 2023/11/20* @Param:* @Return:*/
@RestController
@RequestMapping("/api/user/login")
@RequiredArgsConstructor
public class UserLoginController {private final AuthenticationManager authenticationManager;private final JwtUtils jwtUtils;@PostMappingpublic Result<String> doLogin(@RequestBody UserLoginDTO userLoginDTO) {try {UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userLoginDTO.getUsername(), userLoginDTO.getPassword());Authentication authentication = authenticationManager.authenticate(auth);SecurityContextHolder.getContext().setAuthentication(authentication);UserDetails userDetails = (UserDetails) authentication.getPrincipal();//获取用户权限信息String authorityString = "";Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();for (GrantedAuthority authority : authorities) {authorityString = authority.getAuthority();}//用户身份验证成功,生成并返回jwt令牌Map<String, Object> claims = new HashMap<>();claims.put("username", userDetails.getUsername());claims.put("authorityString", authorityString);String jwtToken = jwtUtils.getJwt(claims);return Result.success(jwtToken);} catch (Exception ex) {//用户身份验证失败,返回登陆失败提示return Result.error("用户名或密码错误!");}}
}
JwtAuthenticationFilter
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;import java.io.IOException;
import java.util.Collections;/*** @Description: 自定义token验证过滤器,验证成功后将用户信息放入SecurityContext上下文* @Author: 翰戈.summer* @Date: 2023/11/18* @Param:* @Return:*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {try {//获取请求头中的tokenString jwtToken = request.getHeader("token");if (!StringUtils.hasLength(jwtToken)) {//token不存在,交给其他过滤器处理filterChain.doFilter(request, response);return; //结束方法}//过滤器中无法初始化Bean组件,使用上下文获取JwtUtils jwtUtils = SpringContextUtils.getBean("jwtUtils");if (jwtUtils == null) {throw new RuntimeException();}//解析jwt令牌Claims claims;try {claims = jwtUtils.parseJwt(jwtToken);} catch (Exception ex) {throw new RuntimeException();}//获取用户信息String username = (String) claims.get("username"); //用户名String authorityString = (String) claims.get("authorityString"); //权限信息Authentication authentication = new UsernamePasswordAuthenticationToken(username, null,Collections.singleton(new SimpleGrantedAuthority(authorityString)));//将用户信息放入SecurityContext上下文SecurityContextHolder.getContext().setAuthentication(authentication);filterChain.doFilter(request, response);} catch (Exception ex) {//过滤器中抛出的异常无法被全局异常处理器捕获,直接返回错误结果response.setCharacterEncoding("utf-8");response.setContentType("application/json; charset=utf-8");String value = new ObjectMapper().writeValueAsString(Result.error("用户未登录!"));response.getWriter().write(value);}}
}
SpringContextUtils
import jakarta.annotation.Nonnull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;/*** @Description: 用于创建上下文,实现ApplicationContextAware接口* @Author: 翰戈.summer* @Date: 2023/11/17* @Param:* @Return:*/
@Component
public class SpringContextUtils implements ApplicationContextAware {private static ApplicationContext applicationContext;public static ApplicationContext getApplicationContext() {return applicationContext;}@Overridepublic void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {SpringContextUtils.applicationContext = applicationContext;}@SuppressWarnings("unchecked")public static <T> T getBean(String name) throws BeansException {if (applicationContext == null) {return null;}return (T) applicationContext.getBean(name);}
}
添加自定义token验证过滤器
/*** @Description: 配置SecurityFilterChain过滤器链* @Author: 翰戈.summer* @Date: 2023/11/17* @Param: HttpSecurity* @Return: SecurityFilterChain*/@Beanpublic SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.requestMatchers(HttpMethod.POST, "/api/user/login").permitAll() //登录放行.anyRequest().authenticated());httpSecurity.authenticationProvider(authenticationProvider());//禁用登录页面httpSecurity.formLogin(AbstractHttpConfigurer::disable);//禁用登出页面httpSecurity.logout(AbstractHttpConfigurer::disable);//禁用sessionhttpSecurity.sessionManagement(AbstractHttpConfigurer::disable);//禁用httpBasichttpSecurity.httpBasic(AbstractHttpConfigurer::disable);//禁用csrf保护httpSecurity.csrf(AbstractHttpConfigurer::disable);//通过上下文获取AuthenticationManagerAuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");//添加自定义token验证过滤器httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);return httpSecurity.build();}
AuthEntryPointHandler
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import java.io.IOException;/*** @Description: 自定义用户未登录的处理(未携带token)* @Author: 翰戈.summer* @Date: 2023/11/19* @Param:* @Return:*/
@Component
public class AuthEntryPointHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {response.setCharacterEncoding("utf-8");response.setContentType("application/json; charset=utf-8");String value = new ObjectMapper().writeValueAsString(Result.error("未携带token!"));response.getWriter().write(value);}
}
AuthAccessDeniedHandler
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import java.io.IOException;/*** @Description: 自定义用户权限不足的处理* @Author: 翰戈.summer* @Date: 2023/11/19* @Param:* @Return:*/
@Component
public class AuthAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {response.setCharacterEncoding("utf-8");response.setContentType("application/json; charset=utf-8");String value = new ObjectMapper().writeValueAsString(Result.error("权限不足!"));response.getWriter().write(value);}
}
添加自定义处理器
/*** @Description: 配置SecurityFilterChain过滤器链* @Author: 翰戈.summer* @Date: 2023/11/17* @Param: HttpSecurity* @Return: SecurityFilterChain*/@Beanpublic SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.requestMatchers(HttpMethod.POST, "/api/user/login").permitAll() //登录放行.anyRequest().authenticated());httpSecurity.authenticationProvider(authenticationProvider());//禁用登录页面httpSecurity.formLogin(AbstractHttpConfigurer::disable);//禁用登出页面httpSecurity.logout(AbstractHttpConfigurer::disable);//禁用sessionhttpSecurity.sessionManagement(AbstractHttpConfigurer::disable);//禁用httpBasichttpSecurity.httpBasic(AbstractHttpConfigurer::disable);//禁用csrf保护httpSecurity.csrf(AbstractHttpConfigurer::disable);//通过上下文获取AuthenticationManagerAuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");//添加自定义token验证过滤器httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);//自定义处理器httpSecurity.exceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler(authAccessDeniedHandler) //处理用户权限不足.authenticationEntryPoint(authEntryPointHandler) //处理用户未登录(未携带token));return httpSecurity.build();}
静态资源放行
/*** 静态资源放行*/@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web) -> web.ignoring().requestMatchers("/doc.html","/doc.html/**","/v3/api-docs","/v3/api-docs/**","/webjars/**","/authenticate","/swagger-ui.html/**","/swagger-resources","/swagger-resources/**");}