springSecurity+jwt使用小结
作者:v_lazy
(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; } }
到此这篇关于springSecurity+jwt使用小结的文章就介绍到这了,更多相关springSecurity使用jwt内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希
您可能感兴趣的文章:
- SpringSecurity+Redis+Jwt实现用户认证授权
- springboot+springsecurity+mybatis+JWT+Redis 实现前后端离实战教程
- SpringBoot3.0+SpringSecurity6.0+JWT的实现
- SpringSecurity整合JWT的使用示例
- SpringBoot整合SpringSecurity和JWT和Redis实现统一鉴权认证
- SpringBoot+SpringSecurity+jwt实现验证
- SpringSecurity详解整合JWT实现全过程
- mall整合SpringSecurity及JWT认证授权实战下
- mall整合SpringSecurity及JWT实现认证授权实战
- Java SpringSecurity+JWT实现登录认证