SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
介紹
Spring Security是一個(gè)強(qiáng)大且靈活的身份驗(yàn)證和訪問控制框架,用于Java應(yīng)用程序。它是基于Spring框架的一個(gè)子項(xiàng)目,旨在為應(yīng)用程序提供安全性。
Spring Security致力于為Java應(yīng)用程序提供認(rèn)證和授權(quán)功能。開發(fā)者可以輕松地為應(yīng)用程序添加強(qiáng)大的安全性,以滿足各種復(fù)雜的安全需求。
SpringSecurity完整流程
- JwtAuthenticationTokenFilter:這里是我們自己定義的過(guò)濾器,主要負(fù)責(zé)放行不攜帶token的請(qǐng)求(如注冊(cè)或登錄請(qǐng)求),并對(duì)攜帶token的請(qǐng)求設(shè)置授權(quán)信息
- UsernamePasswordAuthenticationFilter:負(fù)責(zé)處理我們?cè)诘顷戫?yè)面填寫了用戶名密碼后的登陸請(qǐng)求。入門案例的認(rèn)證工作主要由它負(fù)責(zé)
- ExceptionTranslationFilter:處理過(guò)濾器鏈中拋出的任何AccessDeniedException和AuthenticationException
- FilterSecurityInterceptor:負(fù)責(zé)權(quán)限校驗(yàn)的過(guò)濾器。
一般認(rèn)證工作流程
Authentication接口:它的實(shí)現(xiàn)類表示當(dāng)前訪問系統(tǒng)的用戶,封裝了用戶相關(guān)信息。
AuthenticationManager接口:定義了認(rèn)證Authentication的方法
UserDetailsService接口:加載用戶特定數(shù)據(jù)的核心接口。里面定義了一個(gè)根據(jù)用戶名查詢用戶信息的方法。
UserDetails接口:提供核心用戶信息。通過(guò)UserDetailsService根據(jù)用戶名獲取處理的用戶信息要封裝成UserDetails對(duì)象返回。然后將這些信息封裝到Authentication對(duì)象中。
數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)的采用**RBAC權(quán)限模型(基于角色的權(quán)限控制)**進(jìn)行設(shè)計(jì)。
RBAC至少需要三張表:用戶表–角色表–權(quán)限表(多對(duì)多的關(guān)系比較合理)
- 用戶表(user):存儲(chǔ)用戶名、密碼等基礎(chǔ)信息,進(jìn)行登錄校驗(yàn)
- 角色表(role):對(duì)用戶的角色進(jìn)行分配
- 權(quán)限表(menu):存儲(chǔ)使用不同功能所需的權(quán)限
注冊(cè)流程
配置匿名訪問
在配置類中允許注冊(cè)請(qǐng)求可以匿名訪問
編寫實(shí)現(xiàn)類
registerDTO中存在字符串roleId和實(shí)體類user,先取出user判斷是否存在相同手機(jī)號(hào)。若該手機(jī)號(hào)沒有注冊(cè)過(guò)用戶,對(duì)密碼進(jìn)行加密后即可將用戶存入數(shù)據(jù)庫(kù)。
創(chuàng)建register方法映射,保存用戶的同時(shí)也要將roleId一并存入關(guān)系表中,使用戶獲得對(duì)應(yīng)角色。如下圖。
@Override public Result register(RegisterDTO registerDTO) { // 獲取Map中的數(shù)據(jù) User user = registerDTO.getUser(); String roleId = registerDTO.getRoleId(); // 判斷是否存在相同手機(jī)號(hào) User dataUser = lambdaQuery() .eq(User::getUserPhone, user.getUserPhone()).one(); if (!Objects.isNull(dataUser)) { return Result.fail("該手機(jī)號(hào)已注冊(cè)過(guò)用戶,請(qǐng)勿重復(fù)注冊(cè)"); } // 密碼加密 user.setUserPassword(passwordEncoder .encode(user.getUserPassword())); // 將用戶及對(duì)應(yīng)角色存入數(shù)據(jù)庫(kù) save(user); userMapper.register(user.getUserPhone(), roleId); return Result.ok("注冊(cè)成功"); }
登錄流程
配置匿名訪問
在配置類中允許登錄請(qǐng)求可以匿名訪問
調(diào)用UserDetailsServiceImpl
登錄流程一般對(duì)應(yīng)認(rèn)證工作流程
@Resource private AuthenticationManager authenticationManager; @Resource private StringRedisTemplate stringRedisTemplate; @Resource private PasswordEncoder passwordEncoder; @Resource private UserMapper userMapper; @Override public Result login(User user) { //AuthenticationManager 進(jìn)行用戶認(rèn)證,校驗(yàn)手機(jī)號(hào)和密碼是否正確 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserPhone(), user.getUserPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); //認(rèn)證失敗給出提示 if (Objects.isNull(authenticate)) { throw new RuntimeException("用戶名或密碼錯(cuò)誤"); } //認(rèn)證通過(guò),生成jwt并返回 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userId = loginUser.getUser().getUserId(); String jwtToken = JwtUtil.createToken(userId); Map<String, String> map = new HashMap<>(); stringRedisTemplate.opsForValue() .set(LOGIN_CODE_KEY + userId, JSONUtil.toJsonStr(loginUser)); map.put("token", jwtToken); return Result.ok(map); }
先看這段代碼: UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserPhone(), user.getUserPassword());
這里先用用戶手機(jī)號(hào)和密碼生成UsernamePasswordAuthenticationToken
再看這段代碼:Authentication authenticate = authenticationManager.authenticate(authenticationToken);
利用authenticate調(diào)用自定義實(shí)現(xiàn)類UserDetailsServiceImpl,根據(jù)用戶名判斷用戶是否存在(對(duì)應(yīng)認(rèn)證流程的1、2、3、4)
實(shí)現(xiàn)UserDetailsServiceImpl
由于試下的是UserDetailsService接口,所以必須實(shí)現(xiàn)其方法loadUserByUsername(根據(jù)用戶名查詢數(shù)據(jù)庫(kù)是否存在)這里我傳入的是手機(jī)號(hào)。數(shù)據(jù)庫(kù)中若存在用戶,則返回UserDetails對(duì)象(這里的權(quán)限信息暫且不看,對(duì)應(yīng)認(rèn)證流程的5、5.1、5.2、6)
UserDetails對(duì)象返回后,authenticate方法會(huì)默認(rèn)通過(guò)PasswordEncoder比對(duì)UserDetails與Authentication的密碼是否相同。因?yàn)閁serDetails是通過(guò)自定義實(shí)現(xiàn)類從數(shù)據(jù)庫(kù)中查詢出的user對(duì)象,而Authentication相當(dāng)于是用戶輸入的用戶名和密碼,也就可以理解為通過(guò)前面自定義實(shí)現(xiàn)類利用用戶名查詢到用戶后,再看這個(gè)用戶的密碼是否正確。如果用戶名或密碼不正確,authenticate將會(huì)為空,則拋出異常信息。(對(duì)應(yīng)認(rèn)證流程的7)
由于這里的登錄流程不涉及8,9,10,所以不再敘述。
在剩下的代碼中我們利用用userId生成了jwt的令牌token,將其存入Redis中并返回token給前端。
登出流程
編寫過(guò)濾器
除login、register請(qǐng)求外的所有請(qǐng)求都需要攜帶token才能訪問,因此需要設(shè)計(jì)token攔截器代碼,如下。
對(duì)于不攜帶token的請(qǐng)求(如登錄/注冊(cè))直接放行;對(duì)于攜帶token的請(qǐng)求先判斷該用戶是否登錄,即redis中是否存在相關(guān)信息,若存在,將用戶授權(quán)信息存入SecurityContextHolder,方便用戶授權(quán),最后直接放行。
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Resource private StringRedisTemplate stringRedisTemplate; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 獲取token String token = request.getHeader("token"); if (!StringUtils.hasText(token)) { // 沒有token,放行 filterChain.doFilter(request, response); return; } // 解析token String userId = null; try { userId = JwtUtil.parseJwt(token); } catch (Exception e) { e.printStackTrace(); System.out.println("token非法:" + e); } // 從redis中獲取用戶信息 String userJson = stringRedisTemplate .opsForValue().get(LOGIN_CODE_KEY + userId); LoginUser loginUser = JSONUtil.toBean(userJson, LoginUser.class); if (Objects.isNull(loginUser)) { throw new RuntimeException("用戶未登錄"); } // 存入SecurityContextHolder,設(shè)置用戶授權(quán)信息 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); // 放行 filterChain.doFilter(request, response); } }
此外,還需將token攔截器設(shè)置在過(guò)濾器UsernamePasswordAuthenticationFilter的前面。
編寫實(shí)現(xiàn)類
@Override public Result logout() { // 獲取SecurityContextHolder中的用戶id UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); String userId = loginUser.getUser().getUserId(); // 刪除redis中的值 stringRedisTemplate .delete(LOGIN_CODE_KEY + userId); return Result.ok("注銷成功"); }
獲取SecurityContextHolder中的用戶id后,刪除redis中存儲(chǔ)的值,即登出成功。
授權(quán)流程
確保實(shí)現(xiàn)類正確編寫:
@Data @NoArgsConstructor public class LoginUser implements UserDetails { private User user; private List<String> permissions; public LoginUser(User user, List<String> permissions) { this.user = user; this.permissions = permissions; } @JsonIgnore private List<SimpleGrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { if (authorities != null) { return authorities; } // 把permissions中String類型的權(quán)限信息封裝成SimpleGrantedAuthority對(duì)象 authorities = permissions.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return authorities; } @Override public String getPassword() { return user.getUserPassword(); } @Override public String getUsername() { return user.getUserName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
在token攔截器中,我們添加了這段代碼。
// 存入SecurityContextHolder,設(shè)置用戶授權(quán)信息 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken);
這樣非登錄/注冊(cè)請(qǐng)求都會(huì)被設(shè)置授權(quán)信息。
為對(duì)應(yīng)接口添加注解@PreAuthorize,就會(huì)檢驗(yàn)該請(qǐng)求是否存在相關(guān)請(qǐng)求。
完整代碼
config類
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Resource private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Resource private AccessDeniedHandlerImpl accessDeniedHandler; @Resource private AuthenticationEntryPointImpl authenticationEntryPoint; @Bean public PasswordEncoder passwordEncoder() { // 實(shí)例化PasswordEncoder return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http //關(guān)閉csrf .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/user/login", "/user/register").anonymous() .anyRequest().authenticated(); // 添加過(guò)濾器 http .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 配置異常處理器 http .exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); // 允許跨域 http.cors(); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { // 配置身份驗(yàn)證管理器 return authenticationConfiguration.getAuthenticationManager(); } }
controller類
@RestController @RequestMapping("/user") public class UserController { @Resource private IUserService userService; @PostMapping("/login") public Result login(@RequestBody User user) { return userService.login(user); } @GetMapping("/logout") public Result logout() { return userService.logout(); } @PostMapping("/register") public Result register(@RequestBody RegisterDTO registerDTO) { return userService.register(registerDTO); } }
dto類
@Data @NoArgsConstructor @AllArgsConstructor public class RegisterDTO { private User user; private String roleId; }
/** * @author modox * @date 2023年6月1日 * @description 封裝結(jié)果后返回 */ @Data @NoArgsConstructor @AllArgsConstructor public class Result { public static final Integer SUCCESS_CODE = 200; // 訪問成功狀態(tài)碼 public static final Integer TOKEN_ERROR = 400; // Token錯(cuò)誤狀態(tài)碼 public static final Integer ERROR_CODE = 500; // 訪問失敗狀態(tài)碼 private Integer status; // 狀態(tài)碼 private String msg; // 提示消息 private Object data = null; public Result(Integer status, String msg) { this.status = status; this.msg = msg; } public static Result ok(Integer status,String msg,Object data){ return new Result(status,msg,data); } public static Result ok(String msg,Object data){ return new Result(SUCCESS_CODE,msg,data); } public static Result ok(Object data){ return new Result(SUCCESS_CODE,"操作成功",data); } public static Result ok(){ return new Result(SUCCESS_CODE,"操作成功",null); } public static Result fail(Integer status,String msg){ return new Result(status,msg); } public static Result fail(String msg){ return new Result(ERROR_CODE,msg); } public static Result fail(){ return new Result(ERROR_CODE,"操作失敗"); } public static Map<String,Object> ok(Map<String,Object> map){ map.put("status",SUCCESS_CODE); map.put("msg","查詢成功"); return map; } public static Map<String,Object> ok(PageInfo pageInfo){ Map<String,Object> map = new HashMap<>(); map.put("status",SUCCESS_CODE); map.put("msg","查詢成功"); map.put("count",pageInfo.getTotal()); map.put("data",pageInfo.getList()); return map; } }
entity類
UserDetails的實(shí)現(xiàn)類
@Data @NoArgsConstructor public class LoginUser implements UserDetails { private User user; private List<String> permissions; public LoginUser(User user, List<String> permissions) { this.user = user; this.permissions = permissions; } @JsonIgnore private List<SimpleGrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { if (authorities != null) { return authorities; } // 把permissions中String類型的權(quán)限信息封裝成SimpleGrantedAuthority對(duì)象 authorities = permissions.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return authorities; } @Override public String getPassword() { return user.getUserPassword(); } @Override public String getUsername() { return user.getUserName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
@Data @NoArgsConstructor @AllArgsConstructor @TableName(value = "grd_menu") public class Menu { @TableId private String menuId; private String menuName; private String menuPerms; }
@Data @NoArgsConstructor @AllArgsConstructor @TableName(value = "grd_user") public class User { @TableId(type = IdType.ASSIGN_ID) private String userId; private String userName; private Integer userSex; private String userPhone; private String userPassword; private String userSchool; private Byte[] userImage; }
filter類
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Resource private StringRedisTemplate stringRedisTemplate; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 獲取token String token = request.getHeader("token"); if (!StringUtils.hasText(token)) { // 沒有token,放行 filterChain.doFilter(request, response); return; } // 解析token String userId = null; try { userId = JwtUtil.parseJwt(token); } catch (Exception e) { e.printStackTrace(); System.out.println("token非法:" + e); } // 從redis中獲取用戶信息 String userJson = stringRedisTemplate .opsForValue().get(LOGIN_CODE_KEY + userId); LoginUser loginUser = JSONUtil.toBean(userJson, LoginUser.class); if (Objects.isNull(loginUser)) { throw new RuntimeException("用戶未登錄"); } // 存入SecurityContextHolder,設(shè)置用戶授權(quán)信息 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); // 放行 filterChain.doFilter(request, response); } }
handler類
@Component public class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { Result result = new Result(HttpStatus.FORBIDDEN.value(), "您的權(quán)限不足"); String json = JSONUtil.toJsonStr(result); // 處理異常 WebUtils.renderString(response, json); } }
@Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { Result result = new Result(HttpStatus.UNAUTHORIZED.value(), "用戶認(rèn)證失敗"); String json = JSONUtil.toJsonStr(result); // 處理異常 WebUtils.renderString(response, json); } }
service實(shí)現(xiàn)類
@Service public class UserDetailsServiceImpl extends ServiceImpl<UserMapper, User> implements UserDetailsService { @Resource private MenuMapper menuMapper; @Override public UserDetails loadUserByUsername(String userPhone) throws UsernameNotFoundException { //根據(jù)用戶名查詢用戶信息 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("user_phone", userPhone); User user = getOne(wrapper); //若數(shù)據(jù)庫(kù)中不存在用戶 if (Objects.isNull(user)) { throw new RuntimeException("該手機(jī)號(hào)未注冊(cè)"); } // 根據(jù)用戶查詢權(quán)限信息 添加到LoginUser中 List<String> list = menuMapper.selectPermsByUserPhone(user.getUserPhone()); // 封裝成UserDetails對(duì)象返回 return new LoginUser(user, list); } }
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Resource private AuthenticationManager authenticationManager; @Resource private StringRedisTemplate stringRedisTemplate; @Resource private PasswordEncoder passwordEncoder; @Resource private UserMapper userMapper; @Override public Result login(User user) { //AuthenticationManager 進(jìn)行用戶認(rèn)證,校驗(yàn)手機(jī)號(hào)和密碼是否正確 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserPhone(), user.getUserPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); //認(rèn)證失敗給出提示 if (Objects.isNull(authenticate)) { throw new RuntimeException("用戶名或密碼錯(cuò)誤"); } //認(rèn)證通過(guò),生成jwt并返回 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userId = loginUser.getUser().getUserId(); String jwtToken = JwtUtil.createToken(userId); Map<String, String> map = new HashMap<>(); stringRedisTemplate.opsForValue() .set(LOGIN_CODE_KEY + userId, JSONUtil.toJsonStr(loginUser)); map.put("token", jwtToken); return Result.ok(map); } @Override public Result logout() { // 獲取SecurityContextHolder中的用戶id UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); String userId = loginUser.getUser().getUserId(); // 刪除redis中的值 stringRedisTemplate .delete(LOGIN_CODE_KEY + userId); return Result.ok("注銷成功"); } @Override public Result register(RegisterDTO registerDTO) { // 獲取Map中的數(shù)據(jù) User user = registerDTO.getUser(); String roleId = registerDTO.getRoleId(); // 判斷是否存在相同手機(jī)號(hào) User dataUser = lambdaQuery() .eq(User::getUserPhone, user.getUserPhone()).one(); if (!Objects.isNull(dataUser)) { return Result.fail("該手機(jī)號(hào)已注冊(cè)過(guò)用戶,請(qǐng)勿重復(fù)注冊(cè)"); } // 密碼加密 user.setUserPassword(passwordEncoder .encode(user.getUserPassword())); // 將用戶及對(duì)應(yīng)角色存入數(shù)據(jù)庫(kù) save(user); userMapper.register(user.getUserPhone(), roleId); return Result.ok("注冊(cè)成功"); } }
utils類
public class JwtUtil { // token失效:24小時(shí) public static final String token = "token"; public static final long EXPIPE = 1000 * 60 * 60 * 10; public static final String APP_SECRET = "modox@ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; /** * 根據(jù)傳入的用戶Id生成token * @param userId * @return JWT規(guī)則生成的token */ public static String createToken(String userId) { String JwtToken = Jwts.builder() .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "HS256") .setSubject("grd_user") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIPE)) .claim("userId", userId) .signWith(SignatureAlgorithm.HS256, APP_SECRET) .compact(); return JwtToken; } /** * 驗(yàn)證token是否有效 * @param jwtToken token字符串 * @return 如果token有效返回true,否則false */ public static boolean checkToken(String jwtToken) { try { if (!StringUtils.hasText(jwtToken)) return false; Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 根據(jù)token獲取User信息 * @param jwtToken token字符串 * @return 解析token獲得的user對(duì)象 */ public static String parseJwt(String jwtToken) { //驗(yàn)證token if (checkToken(jwtToken)) { Claims claims = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken).getBody(); return claims.get("userId").toString(); }else { throw new RuntimeException("超時(shí)或不合法token"); } } }
public class RedisConstants { public static final String LOGIN_CODE_KEY = "login:code:"; public static final Long LOGIN_CODE_TTL = 2L; public static final String LOGIN_USER_KEY = "login:token:"; public static final Long LOGIN_USER_TTL = 36000L; }
public class WebUtils { /** * 將字符串渲染到客戶端 * @param response * @param string * @return */ public static String renderString(HttpServletResponse response, String string) { try { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(string); } catch (IOException e) { e.printStackTrace(); } return null; } }
到此這篇關(guān)于SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)的文章就介紹到這了,更多相關(guān)SpringSecurity+Redis+Jwt認(rèn)證授權(quán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity6.0 如何通過(guò)JWTtoken進(jìn)行認(rèn)證授權(quán)
- springSecurity自定義登錄接口和JWT認(rèn)證過(guò)濾器的流程
- SpringSecurity+jwt+captcha登錄認(rèn)證授權(quán)流程總結(jié)
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫(kù)登錄認(rèn)證的實(shí)現(xiàn)
- SpringBoot+SpringSecurity+JWT實(shí)現(xiàn)系統(tǒng)認(rèn)證與授權(quán)示例
- SpringBoot整合SpringSecurity實(shí)現(xiàn)JWT認(rèn)證的項(xiàng)目實(shí)踐
- SpringSecurity整合jwt權(quán)限認(rèn)證的全流程講解
- SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn)
- SpringSecurity JWT基于令牌的無(wú)狀態(tài)認(rèn)證實(shí)現(xiàn)
相關(guān)文章
基于java實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了基于java實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11深入解析StringBuffer和StringBuilder的區(qū)別
以下是對(duì)java中StringBuffer與StringBuilder的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以參考下2013-07-07使用Spring的ApplicationEvent實(shí)現(xiàn)本地事件驅(qū)動(dòng)的實(shí)現(xiàn)方法
本文介紹了如何使用Spring的ApplicationEvent實(shí)現(xiàn)本地事件驅(qū)動(dòng),通過(guò)自定義事件和監(jiān)聽器,實(shí)現(xiàn)模塊之間的松耦合,提升代碼的可維護(hù)性和擴(kuò)展性。同時(shí)還介紹了異步事件和事件傳遞的相關(guān)知識(shí)2023-04-04一文了解Java讀寫鎖ReentrantReadWriteLock的使用
ReentrantReadWriteLock稱為讀寫鎖,它提供一個(gè)讀鎖,支持多個(gè)線程共享同一把鎖。這篇文章主要講解一下ReentrantReadWriteLock的使用和應(yīng)用場(chǎng)景,感興趣的可以了解一下2022-10-10SpringBoot高級(jí)配置之臨時(shí)屬性、配置文件、日志、多環(huán)境配置詳解
這篇文章主要介紹了SpringBoot高級(jí)配置之臨時(shí)屬性、配置文件、日志、多環(huán)境配置,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02Javax Validation自定義注解進(jìn)行身份證號(hào)校驗(yàn)
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)Javax Validation自定義注解進(jìn)行身份證號(hào)校驗(yàn),文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2024-10-10SpringBoot中Elasticsearch的連接配置原理與使用詳解
Elasticsearch是一種開源的分布式搜索和數(shù)據(jù)分析引擎,它可用于全文搜索、結(jié)構(gòu)化搜索、分析等應(yīng)用場(chǎng)景,本文主要介紹了SpringBoot中Elasticsearch的連接配置原理與使用詳解,感興趣的可以了解一下2023-09-09Spring中@Autowired和@Resource注解相同點(diǎn)和不同點(diǎn)
這篇文章主要介紹了Spring中@Autowired和@Resource注解相同點(diǎn)和不同點(diǎn),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01springboot 中 inputStream 神秘消失之謎(終破)
這篇文章主要介紹了springboot 中 inputStream 神秘消失之謎,為了能夠把這個(gè)問題說(shuō)明,我們首先需要從簡(jiǎn)單的http調(diào)用說(shuō)起,通過(guò)設(shè)置body等一些操作,具體實(shí)現(xiàn)代碼跟隨小編一起看看吧2021-08-08