日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Security+jwt+验证码实现验证和授权

發布時間:2025/4/16 69 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Security+jwt+验证码实现验证和授权 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

微服務Security+jwt+驗證碼實現認證和授權

  • 簡要介紹
  • 基本流程
  • 核心代碼
  • 測試

簡要介紹

本次博客采用Spring Security、jwt、驗證碼的形式實現登錄驗證,項目本身是一個前后端分離項目。如果你的項目在登陸時不需要驗證碼,你只需要在后續的代碼中,將有關驗證碼的過濾器刪除。
gitee倉庫連接

基本流程

1、前端請求后端"/captcha"驗證碼接口,后端生成驗證碼文本及編碼并將其存入redis緩存,然后返回驗證碼文本(五個字符)和驗證碼base64編碼給前端。
2、前端顯示驗證碼圖片,用戶輸入用戶名、密碼、驗證碼點擊登錄。
3、后端開啟驗證
(1)開啟驗證碼驗證,走驗證碼過濾器,如果正確則放行走下一個過濾器,如果錯誤則拋出異常給登錄失敗過濾器,返回失敗信息給前端。
(2)開啟jwt驗證。

a:如果請求沒有攜帶token,則認為是首次登錄,jwt過濾器不做任何事情,放行走UsernamePasswordAuthenticationFilter過濾器,該過濾器會通過查數據庫驗證用戶的身份信息決定用戶是否能登錄。如果驗證成功會生成一個Authentication,并保存在SecurityContext(security上下文)中。Authentication包含用戶的信息及權限
b:如果請求中攜帶了token,走jwt過濾器,過濾器判斷jwt是否為空、攜帶信息(用戶名)是否為空,jwt是否過期,如果上述條件都正常,創建一個Authentication的實現類對象,并通過自定義的獲取用戶權限方法獲取權限,然后通過userDetailService的loadUserByUsername方法得到UserDetails對象,里面包含用戶信息和權限,調用Authentication的setUserDetails方法,最后將該Authentication對象存入到Security上下文中,后續的過濾器查詢到該Authentication,就會直接放行,比如UsernamePasswordAuthenticationFilter過濾器。

上述認證都是由過濾器完成,因為認證是有順序的,所以在security配置文件中我們要設置這三個過濾器的順序為:驗證碼過濾器=》jwt過濾器=》UsernamePasswordAuthenticationFilter

.addFilterBefore(captchaFilter,UsernamePasswordAuthenticationFilter.class) .addFilterAt(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

.addFilterAt(a,b)默認會將a設置在b之前。

至此登錄認證就結束了,需要注意的是,我們在認證的時候已經將用戶的權限列表加入到了Authentication并放在了Security上下文中,所以后續對于資源做權限判斷時時,只需要再目標接口上加入一下注解實現。@PreAuthorize(“hasAuthority(‘sys:role:list’)”)
@PreAuthorize(“hasRole(‘ROLE_admin’)”)

當請求該接口時,security就會去Authentication中查詢有無該權限或者該角色。

核心代碼

1、驗證碼過濾器
該過濾器用于驗證驗證碼是否正確。該過濾器繼承的是OncePerRequestFilter,因為每次登錄只需要驗證一次。

package com.komorebi.security;import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.komorebi.common.CaptchaException; import com.komorebi.common.Const; import com.komorebi.utils.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;/*CaptchaFilter用于驗證碼驗證 * 因為驗證碼值需要一次校驗,所以繼承OncePerRequestFilter * 自己寫的過濾器要在security配置類中配置 * */ @Slf4j @Component public class CaptchaFilter extends OncePerRequestFilter {@AutowiredRedisUtil redisUtil;@AutowiredLoginFailureHandler loginFailureHandler;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {log.info("開始驗證碼驗證");String url = request.getRequestURI();//只有登陸時才會驗證驗證碼if("/login".equals(url) && request.getMethod().equals("POST")){try{//校驗驗證碼validate(request);}catch (CaptchaException e){//發現異常,則交給登錄失敗處理器loginFailureHandler.onAuthenticationFailure(request,response,e);}}//將請求轉發給下一個過濾器filterChain.doFilter(request,response);}//校驗驗證碼邏輯private void validate(HttpServletRequest request) {String key = request.getParameter("tokens");String code = request.getParameter("code");if(StringUtils.isBlank(key) || StringUtils.isBlank(code)){throw new CaptchaException("驗證碼信息為空");}if(!code.equals(redisUtil.hget(Const.CAPTCHA_KEY,key))){throw new CaptchaException("驗證碼錯誤");}//保證每個驗證碼只使用一次:安全redisUtil.del(Const.CAPTCHA_KEY,key);} }

2、jwt過濾器

package com.komorebi.security;import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.komorebi.entity.SysUser; import com.komorebi.mapper.SysUserMapper; import com.komorebi.service.SysUserService; import com.komorebi.utils.JwtUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;//jwt認證過濾器 @Slf4j public class JwtAuthenticationFilter extends BasicAuthenticationFilter {@AutowiredJwtUtils jwtUtils;@AutowiredUserDetailServiceImpl userDetailService;@AutowiredSysUserService sysUserService;public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {log.info("開啟jwt認證");String jwt = request.getHeader(jwtUtils.getHeader());if(jwt != null){Claims claim = jwtUtils.getClaimByToken(jwt);if(claim != null){String username = claim.getSubject();log.info("jwt認證:檢查用戶名");if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){SysUser sysUser = sysUserService.getByUserName(username);Long userId = sysUser.getId();UserDetails userDetails = userDetailService.loadUserByUsername(username);if(!jwtUtils.isTokenExpired(claim)){UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(userId));auth.setDetails(userDetails);log.info("通過jwt認證,設置Authentication,后續過濾器放行");SecurityContextHolder.getContext().setAuthentication(auth);}}}}else {log.info("首次登陸 jwt為空");}chain.doFilter(request,response);} }

由于該過濾器我們繼承于BasicAuthenticationFilter,也可以繼承BasicAuthenticationFilter 類,并且重寫了構造函數,所以在SecurityConfig中要采用Bean注入。對應SecurityConfig文件

@BeanJwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {return new JwtAuthenticationFilter(authenticationManager());}

3、UserDetailServiceImpl
UsernamePasswordAuthenticationFilter主要功能為:用戶登錄信息驗證,獲取用戶權限,并將上述信息封裝為UserDetails,然后生成Authentication,將UserDetails加入到Authentication中,最終將Authentication加入到security上下文中。
封裝為UserDetails的功能是通過UserDetailService實現的,因為UserDetailService是接口,所以定義UserDetailServiceImpl實現該接口,即UserDetailServiceImpl用于驗證用戶信息和獲取用用戶權限。

package com.komorebi.security;import com.komorebi.entity.SysUser; import com.komorebi.service.SysUserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; 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.Component; import org.springframework.stereotype.Service;import java.util.ArrayList; import java.util.List; @Slf4j @Component public class UserDetailServiceImpl implements UserDetailsService {@AutowiredSysUserService userService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {log.info("開始登陸驗證,用戶名為: {}",username);SysUser user = userService.getByUserName(username);if(user == null){log.info("用戶名或密碼不正確");throw new UsernameNotFoundException("用戶名或密碼不正確");}//UserDetails是接口,User是它的一個實現類//將用戶密碼告訴springSecurity//剩下的認證 就由框架Security幫我們完成return new User(user.getUsername(),user.getPassword(),getUserAuthority(user.getId()));}/*獲取用戶權限信息(角色,菜單權限)* */public List<GrantedAuthority> getUserAuthority(Long userId){//返回擁有角色和權限,逗號分隔String authority = userService.getUserAuthority(userId);//AuthorityUtils.commaSeparatedStringToAuthorityList將逗號分隔的字符串轉為權限列表return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);} }

4、登陸成功過濾器

package com.komorebi.security;import cn.hutool.json.JSONUtil; import com.komorebi.common.Result; import com.komorebi.utils.JwtUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component;import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Componentpublic class LoginSuccessHandler implements AuthenticationSuccessHandler{@AutowiredJwtUtils jwtUtils;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = response.getOutputStream();String username = authentication.getName();// 生成jwt,并放置到請求頭中String jwt = jwtUtils.generateToken(username);//將jwt放入response header中:Authorizationresponse.setHeader(jwtUtils.getHeader(), jwt);Result result = Result.success("登陸成功過濾器執行");outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));outputStream.flush();outputStream.close();} }

這里,我們過濾器需要向前端傳遞json數據,但是security是不支持return json數據的,所以我們只能通過流的方式返回數據。
基本步驟:
(1)獲取reponse的字節輸出流
(2)創建返回對象
(3)將對象轉為字節數組輸出給response
(4)刷新緩沖區,關閉流
后續的過濾器只要涉及到返回數據給前端,都會使用該方法。
5、登陸失敗過濾器

package com.komorebi.security;import cn.hutool.json.JSONUtil; import com.komorebi.common.Result; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component;import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@Component public class LoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");//security的返回值是重定向,對于前后端分離的項目需要返回json數據,所以使用流的形式//因為返回類型是void,并且返回值是json類型數據,所以要使用到流ServletOutputStream outputStream = response.getOutputStream();Result result = Result.fail(exception.getMessage());outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));outputStream.flush();outputStream.close();} }

6、登出成功過濾器
該處理器會返還給前端一個空的jwt,即前端下次請求時jwt為空,代表未登錄。如果是將jwt存在redis中,還要清除緩存。

package com.komorebi.security;import cn.hutool.json.JSONUtil; import com.komorebi.common.Const; import com.komorebi.common.Result; import com.komorebi.utils.JwtUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Component;import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {@AutowiredJwtUtils jwtUtils;@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("退出成功處理器");//如果認證的身份憑證不為空,需要手動退出if(authentication != null){new SecurityContextLogoutHandler().logout(request,response,authentication);}response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = response.getOutputStream();//等處成功將jwt設置為空,因為jwt是無狀態的只能在過期后消失response.setHeader(jwtUtils.getHeader(),"");Result result = Result.success("登出成功");outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));outputStream.flush();outputStream.close();} }

7、權限不足過濾器
該過濾器是用于處理權限不足時的情況。

package com.komorebi.security;import cn.hutool.json.JSONUtil; import com.komorebi.common.Result; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component;import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = response.getOutputStream();//權限不足response.setStatus(HttpServletResponse.SC_FORBIDDEN);Result result = Result.fail(accessDeniedException.getMessage());outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));outputStream.flush();outputStream.close();} }

8、未認證過濾器
該過濾器是用于處理用戶未登錄的情況。

package com.komorebi.security;import cn.hutool.json.JSONUtil; import com.komorebi.common.Result; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component;import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = response.getOutputStream();//未認證response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);Result result = Result.fail("請先登錄");outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));outputStream.flush();outputStream.close();} }

9、JWT工具類

package com.komorebi.utils;import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;import java.util.Date; @Data @Component @ConfigurationProperties(prefix = "komorebi.jwt") //通過配置文件賦值expire、secret public class JwtUtils {//兩個是參數private long expire;//保留天數private String secret;//密鑰private String header;//{header:jwt}傳給前端//生成jwtpublic String generateToken(String username){Date nowDate = new Date();Date expireDate = new Date(nowDate.getTime() * expire);return Jwts.builder().setHeaderParam("typ","JWT").setSubject(username).setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS256,secret).compact();}//解析jwtpublic Claims getClaimByToken(String jwt){try{return Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt).getBody();}catch (Exception e){return null;}}//jwt是否過期public boolean isTokenExpired(Claims claims){//如果過期時間在當前時間之前,就代表過期return claims.getExpiration().before(new Date());} }

expire、secret、header三個變量存放在application.yml文件中

通過該注解@ConfigurationProperties(prefix = “komorebi.jwt”)實現通過配置文件賦值expire、secret、header。
10、SecurityConfig
該配置類會對前面定義的所有過濾器進行配置

package com.komorebi.config;import com.komorebi.security.*; 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.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredLoginSuccessHandler loginSuccessHandler;@AutowiredLoginFailureHandler loginFailureHandler;@AutowiredCaptchaFilter captchaFilter;@AutowiredJwtAccessDeniedHandler jwtAccessDeniedHandler;@AutowiredJwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;@AutowiredUserDetailsService userDetailService;@AutowiredJwtLogoutSuccessHandler jwtLogoutSuccessHandler;@BeanJwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {return new JwtAuthenticationFilter(authenticationManager());}@BeanBCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}//Java數組初始化用的是{花括號}//URL白名單,訪問時不需要攔截private static final String [] URL_WHITELIST = {"/login","/logout","/captcha","/favicon.ico",};@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//設置userDetail和加密方法auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable()//登陸配置.formLogin().successHandler(loginSuccessHandler)//登陸成功處理器.failureHandler(loginFailureHandler)//登錄失敗處理器//退出.and().logout().logoutSuccessHandler(jwtLogoutSuccessHandler)//設置不生成session策略.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//配置攔截規則.and().authorizeRequests().antMatchers(URL_WHITELIST).permitAll()//所有人都可以訪問.anyRequest().authenticated()//需要登陸,即需要認證//異常處理器.and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)//沒有認證.accessDeniedHandler(jwtAccessDeniedHandler)//沒有權限//配置自定義的過濾器=》前置過濾器.and().addFilterBefore(captchaFilter,UsernamePasswordAuthenticationFilter.class).addFilterAt(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);} }

測試

1、當驗證碼錯誤時

2、驗證碼、密碼正確時(沒有攜帶jwt登錄)

此時會返回token

3、攜帶jwt請求后端接口

@RestController @RequestMapping("/test") public class TestController {@AutowiredSysUserService sysUserService;@GetMapping@PreAuthorize("hasRole('admin')")public Object test(){return Result.success(sysUserService.list());} }

/test接口需要用戶具備admin角色,此處登錄的用戶具有該角色,則訪問成功。

4、對于需要某種權限或者角色才能訪問的后端接口只需要在節后上面進行設置

(1)需要權限才能訪問

//新增@PostMapping("/save")@PreAuthorize("hasAuthority('sys:role:save')")public Result save(@Validated @RequestBody SysRole sysRole){//添加時設置create時間sysRole.setCreated(LocalDateTime.now());sysRole.setStatu(Const.STATUS_ON);sysRoleService.save(sysRole);return Result.success(sysRole);}

(2)需要用于某種角色才可以訪問

@RestController @RequestMapping("/test") public class TestController {@AutowiredSysUserService sysUserService;@GetMapping@PreAuthorize("hasRole('admin')")public Object test(){return Result.success(sysUserService.list());} }

此處登錄的用戶不具備該角色,所以會走權限不足過濾器

總結

以上是生活随笔為你收集整理的Security+jwt+验证码实现验证和授权的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。