springSecurity+jwt使用小結(jié)
(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) 流程說明(可以對(duì)照代碼實(shí)現(xiàn))
1.springboot啟動(dòng)時(shí),會(huì)先加載WebSecurityConfig配置
(1)WebSecurityConfig里會(huì)跳過指定的url【requestMatchers("/auth/login").permitAll()】
(2)增加過濾器【.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)】
(3)綁定認(rèn)證失敗類:exceptionHandling(exce->exce.authenticationEntryPoint(unauthorizedHandler))
(4)綁定權(quán)限校驗(yàn)失敗類:exceptionHandling(exce->exce.accessDeniedHandler(wAccessDeniedHandler))
2.當(dāng)/auth/login請(qǐng)求進(jìn)入時(shí),會(huì)先到JwtAuthenticationTokenFilter過濾器,判斷請(qǐng)求頭中是否有token,因?yàn)闆]有token直接filterChain.doFilter(request, response)下一步,又因?yàn)樵赪ebSecurityConfig配置了過濾,不會(huì)進(jìn)入異常類,會(huì)直接到達(dá)AuthController,會(huì)進(jìn)入到LoginServiceImpl類,根據(jù)用戶名和密碼進(jìn)行校驗(yàn)【authenticationManager.authenticate(authentication),真正進(jìn)行校驗(yàn)的實(shí)現(xiàn)類JwtAuthenticationProvider】,校驗(yàn)通過則返回token,否則拋出異常,返回錯(cuò)誤信息。
3.當(dāng)其它請(qǐng)求進(jìn)入時(shí),也會(huì)先到JwtAuthenticationTokenFilter過濾器,如果有token,則解析token,獲取用戶信息,然后設(shè)置到SecurityContextHolder中,如果解析失敗,則拋出異常,進(jìn)入異常處理類,返回錯(cuò)誤信息。如果沒有token,則會(huì)被攔截,進(jìn)入異常處理類,返回錯(cuò)誤信息
(3) 代碼實(shí)現(xiàn)
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; /** * 認(rèn)證管理 * @param configuration * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } /** * 認(rèn)證過濾器 * @return */ @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){ return new JwtAuthenticationTokenFilter(); } /** * 密碼加密 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 權(quán)限校驗(yàn) * @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) //綁定認(rèn)證失敗類 // .exceptionHandling(exce->exce.authenticationEntryPoint(unauthorizedHandler)) //鑒權(quán)失敗類 .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; /** * * 身份驗(yàn)證攔截器 */ @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 ; } //校驗(yàn)token格式 if(!authorization.startsWith("Bearer ")){ log.error(RespCodeEnum.TOKEN_ERROR.getDesc()); response(response, RespCodeEnum.TOKEN_ERROR); return ; } //獲取jwt數(shù)據(jù) 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); } //獲取用戶名和過期時(shí)間 JWT jwt = JWTUtil.parseToken(token); String loginname = (String) jwt.getPayload("loginname"); //獲取jwt中的過期時(shí)間 long exp = ((NumberWithFormat) jwt.getPayload("exp")).longValue(); //判斷是否已經(jīng)過期 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()); //將認(rèn)證過了憑證保存到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); // 或者使用自定義狀態(tài)碼 response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); response.getWriter().write(JSONUtil.toJsonStr(ResponseDto.fail(error))); } }
3.自定義身份驗(yàn)證失敗處理器類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; /** * * 自定義身份驗(yàn)證失敗處理器 */ @Component public class UnauthorizedHandler implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // 設(shè)置響應(yīng)狀態(tài)碼為401(未授權(quán)) response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 設(shè)置響應(yīng)內(nèi)容類型 response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); // 響應(yīng)體內(nèi)容,可以根據(jù)需要自定義 ResponseDto fail = ResponseDto.fail(RespCodeEnum.ACCESS_DENIED); response.getWriter().write(JSONUtil.toJsonStr(fail)); } }
4.權(quán)限認(rèn)證失敗處理類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; /** * * 權(quán)限認(rèn)證失敗處理 */ @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("認(rèn)證失敗"); response.getWriter().flush(); } }
5.JwtAuthenticationProvider實(shí)現(xiàn)AuthenticationProvider接口,進(jìn)行用戶身份驗(yàn)證
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; /** * 用戶身份驗(yàn)證 * */ @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,從數(shù)據(jù)庫獲取用戶信息
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.自定義權(quán)限校驗(yàn)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.實(shí)現(xiàn)登錄接口和接口權(quán)限校驗(yàn)
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; /** * 認(rèn)證控制器 * */ @RestController @RequestMapping("auth") @RequiredArgsConstructor public class AuthController { private final ILoginService loginService; /** * 登錄 * @param req 請(qǐng)求參數(shù) * @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.登錄實(shí)現(xiàn)
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) { //登錄驗(yàn)證 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)) //過期時(shí)間3小時(shí) .setExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) .sign(); return token; } }
到此這篇關(guān)于springSecurity+jwt使用小結(jié)的文章就介紹到這了,更多相關(guān)springSecurity使用jwt內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- springboot+springsecurity+mybatis+JWT+Redis?實(shí)現(xiàn)前后端離實(shí)戰(zhàn)教程
- SpringBoot3.0+SpringSecurity6.0+JWT的實(shí)現(xiàn)
- SpringSecurity整合JWT的使用示例
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringBoot+SpringSecurity+jwt實(shí)現(xiàn)驗(yàn)證
- SpringSecurity詳解整合JWT實(shí)現(xiàn)全過程
- mall整合SpringSecurity及JWT認(rèn)證授權(quán)實(shí)戰(zhàn)下
- mall整合SpringSecurity及JWT實(shí)現(xiàn)認(rèn)證授權(quán)實(shí)戰(zhàn)
- Java SpringSecurity+JWT實(shí)現(xiàn)登錄認(rèn)證
相關(guān)文章
源碼解讀Spring-Integration執(zhí)行過程
Spring-Integration基于Spring,在應(yīng)用程序中啟用了輕量級(jí)消息傳遞,并支持通過聲明式適配器與外部系統(tǒng)集成,今天主要是看個(gè)簡單的hello word進(jìn)來分析下整個(gè)執(zhí)行過程,感興趣的朋友一起看看吧2021-06-06intellij idea14打包apk文件和查看sha1值
這篇文章主要為大家詳細(xì)介紹了intellij idea14打包apk文件和查看sha1值,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10MyBatis基礎(chǔ)支持DataSource實(shí)現(xiàn)源碼解析
這篇文章主要為大家介紹了MyBatis基礎(chǔ)支持DataSource實(shí)現(xiàn)源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Java技能點(diǎn)之SimpleDateFormat進(jìn)行日期格式化問題
這篇文章主要介紹了Java技能點(diǎn)之SimpleDateFormat進(jìn)行日期格式化問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04解讀Spring配置文件中的property標(biāo)簽中的屬性
這篇文章主要介紹了Spring配置文件中的property標(biāo)簽中的屬性,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01使用java生成json時(shí)產(chǎn)生棧溢出錯(cuò)誤問題及解決方案
這篇文章主要介紹了使用java生成json時(shí)產(chǎn)生棧溢出錯(cuò)誤問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06SpringBoot單元測(cè)試使用@Test沒有run方法的解決方案
這篇文章主要介紹了SpringBoot單元測(cè)試使用@Test沒有run方法的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01java運(yùn)行時(shí)數(shù)據(jù)區(qū)域和類結(jié)構(gòu)詳解
這篇文章主要介紹了java運(yùn)行時(shí)數(shù)據(jù)區(qū)域和類結(jié)構(gòu),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07