(1) jdk版本和springboot版本
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<springboot.version>3.1.5</springboot.version>
</properties>
(2) 流程说明(可以对照代码实现)
1.springboot启动时,会先加载WebSecurityConfig配置
(1)WebSecurityConfig里会跳过指定的url【requestMatchers("/auth/login").permitAll()】
(2)增加过滤器【.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)】
(3)绑定认证失败类:exceptionHandling(exce->exce.authenticationEntryPoint(unauthorizedHandler))
(4)绑定权限校验失败类:exceptionHandling(exce->exce.accessDeniedHandler(wAccessDeniedHandler))
2.当/auth/login请求进入时,会先到JwtAuthenticationTokenFilter过滤器,判断请求头中是否有token,因为没有token直接filterChain.doFilter(request, response)下一步,又因为在WebSecurityConfig配置了过滤,不会进入异常类,会直接到达AuthController,
会进入到LoginServiceImpl类,根据用户名和密码进行校验【authenticationManager.authenticate(authentication),真正进行校验的实现类JwtAuthenticationProvider】,校验通过则返回token,
否则抛出异常,返回错误信息。
3.当其它请求进入时,也会先到JwtAuthenticationTokenFilter过滤器,
如果有token,则解析token,获取用户信息,然后设置到SecurityContextHolder中,如果解析失败,则抛出异常,进入异常处理类,返回错误信息。
如果没有token,则会被拦截,进入异常处理类,返回错误信息
(3) 代码实现
1.spring Security配置WebSecurityConfig
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.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.config.annotation.web.configurers.HeadersConfigurer;
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
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Autowired
private UnauthorizedHandler unauthorizedHandler;
@Autowired
private WAccessDeniedHandler wAccessDeniedHandler;
/**
* 认证管理
* @param configuration
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
/**
* 认证过滤器
* @return
*/
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
return new JwtAuthenticationTokenFilter();
}
/**
* 密码加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 权限校验
* @return
*/
@Bean("per")
public PermissionCheckServiceImpl permissionCheckServiceImpl(){
return new PermissionCheckServiceImpl();
}
/**
* 配置安全过滤器链
* @param httpSecurity
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManager-> sessionManager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize->authorize
.requestMatchers("/auth/login").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
//禁用缓存
.headers(header->header.cacheControl(HeadersConfigurer.CacheControlConfig::disable))
.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
//绑定认证失败类
// .exceptionHandling(exce->exce.authenticationEntryPoint(unauthorizedHandler))
//鉴权失败类
.exceptionHandling(exce->exce.accessDeniedHandler(wAccessDeniedHandler))
.build();
}
}
2.jwt身份拦截器JwtAuthenticationTokenFilter
import cn.hutool.core.convert.NumberWithFormat;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWTValidator;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
*
* 身份验证拦截器
*/
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException, UsernameNotFoundException {
//从头中获取token(jwt)
String authorization = request.getHeader("Authorization");
//判断token
if(StringUtils.isBlank(authorization)){
filterChain.doFilter(request, response);
return ;
}
//校验token格式
if(!authorization.startsWith("Bearer ")){
log.error(RespCodeEnum.TOKEN_ERROR.getDesc());
response(response, RespCodeEnum.TOKEN_ERROR);
return ;
}
//获取jwt数据
String token = authorization.split(" ")[1];
if(!JWTUtil.verify(token, AuthConstant.JWT_KEY.getBytes(StandardCharsets.UTF_8))){
log.error(RespCodeEnum.TOKEN_ERROR.getDesc());
response(response, RespCodeEnum.TOKEN_ERROR);
}
//获取用户名和过期时间
JWT jwt = JWTUtil.parseToken(token);
String loginname = (String) jwt.getPayload("loginname");
//获取jwt中的过期时间
long exp = ((NumberWithFormat) jwt.getPayload("exp")).longValue();
//判断是否已经过期
if(System.currentTimeMillis() / 1000 > exp){
log.error(RespCodeEnum.TOKEN_EXP.getDesc());
response(response, RespCodeEnum.TOKEN_EXP);
return;
}
//获取用户信息
UserDetailsBo userDetails = (UserDetailsBo)userDetailsService.loadUserByUsername(loginname);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),
userDetails.getPassword(), userDetails.getAuthorities());
authenticationToken.setDetails(userDetails.getUserDto());
//将认证过了凭证保存到security的上下文中以便于在程序中使用
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
private void response(@NotNull HttpServletResponse response,@NotNull RespCodeEnum error) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 或者使用自定义状态码
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(ResponseDto.fail(error)));
}
}
3.自定义身份验证失败处理器类UnauthorizedHandler
import cn.hutool.json.JSONUtil;
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;
/**
*
* 自定义身份验证失败处理器
*/
@Component
public class UnauthorizedHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
// 设置响应状态码为401(未授权)
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 设置响应内容类型
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
// 响应体内容,可以根据需要自定义
ResponseDto fail = ResponseDto.fail(RespCodeEnum.ACCESS_DENIED);
response.getWriter().write(JSONUtil.toJsonStr(fail));
}
}
4.权限认证失败处理类WAccessDeniedHandler
import jakarta.servlet.ServletException;
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;
/**
*
* 权限认证失败处理
*/
@Component
public class WAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().print("认证失败");
response.getWriter().flush();
}
}
5.JwtAuthenticationProvider实现AuthenticationProvider接口,进行用户身份验证
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 用户身份验证
*
*/
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) {
String username = String.valueOf(authentication.getPrincipal());
String password = String.valueOf(authentication.getCredentials());
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if(userDetails != null && StringUtils.isNotBlank(userDetails.getPassword())
&& userDetails.getPassword().equals(password)){
return new UsernamePasswordAuthenticationToken(username,password,authentication.getAuthorities());
}
throw new BusinessException(RespCodeEnum.NAME_OR_PASSWORD_ERROR);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
6.继承UserDetailsService,从数据库获取用户信息
import lombok.RequiredArgsConstructor;
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;
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final IUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDto user = userService.selectUserByLoginName(username);
return new UserDetailsBo(user);
}
}
7.自定义UserDetailsBo类,继承UserDetails
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.Collection;
import java.util.stream.Collectors;
@Component
public class UserDetailsBo implements UserDetails {
private UserDto userDto;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return userDto.getPermissionName().stream()
.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
@Override
public String getPassword() {
return userDto.getPassword();
}
@Override
public String getUsername() {
return userDto.getLoginName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public UserDetailsBo(){}
public UserDetailsBo(UserDto userDto){
this.userDto = userDto;
}
public UserDto getUserDto() {
return userDto;
}
public void setUserDto(UserDto userDto) {
this.userDto = userDto;
}
}
8.自定义权限校验PermissionCheckServiceImpl
import cn.hutool.core.util.ArrayUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.CollectionUtils;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class PermissionCheckServiceImpl {
public PermissionCheckServiceImpl(){}
public boolean havePermission(String... permissions)
{
if(permissions == null){
return true;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null){
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
List<String> authList = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
for(int i = 0;i < permissions.length;i++){
if(authList.contains(permissions[i])){
return true;
}
}
}
return false;
}
}
9.实现登录接口和接口权限校验
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 认证控制器
*
*/
@RestController
@RequestMapping("auth")
@RequiredArgsConstructor
public class AuthController {
private final ILoginService loginService;
/**
* 登录
* @param req 请求参数
* @return 返回token
*/
@GetMapping("login")
public String login(@Validated UserLoginAccPwdDto req) {
return loginService.loginAccPwd(req);
}
@PreAuthorize("@per.havePermission('user','admin')")
@GetMapping("test")
public UserInfoVo test() {
return null;
}
}
10.登录实现
import cn.hutool.jwt.JWT;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements ILoginService {
private final AuthenticationManager authenticationManager;
@Override
public String loginAccPwd(UserLoginAccPwdDto login) {
//登录验证
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(login.getLoginName(), login.getPassword());
authenticationManager.authenticate(authentication);
//生成jwt token
String token = JWT.create()
.setPayload("loginname", login.getLoginName())
.setKey(AuthConstant.JWT_KEY.getBytes(StandardCharsets.UTF_8))
//过期时间3小时
.setExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
.sign();
return token;
}
}
版权归原作者 v_lazy 所有, 如有侵权,请联系我们删除。