SpringSecurity+Redis+Jwt實現(xiàn)用戶認(rèn)證授權(quán)
介紹
Spring Security是一個強大且靈活的身份驗證和訪問控制框架,用于Java應(yīng)用程序。它是基于Spring框架的一個子項目,旨在為應(yīng)用程序提供安全性。
Spring Security致力于為Java應(yīng)用程序提供認(rèn)證和授權(quán)功能。開發(fā)者可以輕松地為應(yīng)用程序添加強大的安全性,以滿足各種復(fù)雜的安全需求。
SpringSecurity完整流程

- JwtAuthenticationTokenFilter:這里是我們自己定義的過濾器,主要負(fù)責(zé)放行不攜帶token的請求(如注冊或登錄請求),并對攜帶token的請求設(shè)置授權(quán)信息
- UsernamePasswordAuthenticationFilter:負(fù)責(zé)處理我們在登陸頁面填寫了用戶名密碼后的登陸請求。入門案例的認(rèn)證工作主要由它負(fù)責(zé)
- ExceptionTranslationFilter:處理過濾器鏈中拋出的任何AccessDeniedException和AuthenticationException
- FilterSecurityInterceptor:負(fù)責(zé)權(quán)限校驗的過濾器。
一般認(rèn)證工作流程
Authentication接口:它的實現(xiàn)類表示當(dāng)前訪問系統(tǒng)的用戶,封裝了用戶相關(guān)信息。
AuthenticationManager接口:定義了認(rèn)證Authentication的方法
UserDetailsService接口:加載用戶特定數(shù)據(jù)的核心接口。里面定義了一個根據(jù)用戶名查詢用戶信息的方法。
UserDetails接口:提供核心用戶信息。通過UserDetailsService根據(jù)用戶名獲取處理的用戶信息要封裝成UserDetails對象返回。然后將這些信息封裝到Authentication對象中。

數(shù)據(jù)庫
數(shù)據(jù)庫的采用**RBAC權(quán)限模型(基于角色的權(quán)限控制)**進(jìn)行設(shè)計。
RBAC至少需要三張表:用戶表–角色表–權(quán)限表(多對多的關(guān)系比較合理)
- 用戶表(user):存儲用戶名、密碼等基礎(chǔ)信息,進(jìn)行登錄校驗
- 角色表(role):對用戶的角色進(jìn)行分配
- 權(quán)限表(menu):存儲使用不同功能所需的權(quán)限

注冊流程
配置匿名訪問
在配置類中允許注冊請求可以匿名訪問

編寫實現(xiàn)類
registerDTO中存在字符串roleId和實體類user,先取出user判斷是否存在相同手機號。若該手機號沒有注冊過用戶,對密碼進(jìn)行加密后即可將用戶存入數(shù)據(jù)庫。
創(chuàng)建register方法映射,保存用戶的同時也要將roleId一并存入關(guān)系表中,使用戶獲得對應(yīng)角色。如下圖。

@Override
public Result register(RegisterDTO registerDTO) {
// 獲取Map中的數(shù)據(jù)
User user = registerDTO.getUser();
String roleId = registerDTO.getRoleId();
// 判斷是否存在相同手機號
User dataUser = lambdaQuery()
.eq(User::getUserPhone, user.getUserPhone()).one();
if (!Objects.isNull(dataUser)) {
return Result.fail("該手機號已注冊過用戶,請勿重復(fù)注冊");
}
// 密碼加密
user.setUserPassword(passwordEncoder
.encode(user.getUserPassword()));
// 將用戶及對應(yīng)角色存入數(shù)據(jù)庫
save(user);
userMapper.register(user.getUserPhone(), roleId);
return Result.ok("注冊成功");
}
登錄流程
配置匿名訪問
在配置類中允許登錄請求可以匿名訪問

調(diào)用UserDetailsServiceImpl
登錄流程一般對應(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)證,校驗手機號和密碼是否正確
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserPhone(), user.getUserPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//認(rèn)證失敗給出提示
if (Objects.isNull(authenticate)) {
throw new RuntimeException("用戶名或密碼錯誤");
}
//認(rèn)證通過,生成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());這里先用用戶手機號和密碼生成UsernamePasswordAuthenticationToken
再看這段代碼:Authentication authenticate = authenticationManager.authenticate(authenticationToken);利用authenticate調(diào)用自定義實現(xiàn)類UserDetailsServiceImpl,根據(jù)用戶名判斷用戶是否存在(對應(yīng)認(rèn)證流程的1、2、3、4)
實現(xiàn)UserDetailsServiceImpl

由于試下的是UserDetailsService接口,所以必須實現(xiàn)其方法loadUserByUsername(根據(jù)用戶名查詢數(shù)據(jù)庫是否存在)這里我傳入的是手機號。數(shù)據(jù)庫中若存在用戶,則返回UserDetails對象(這里的權(quán)限信息暫且不看,對應(yīng)認(rèn)證流程的5、5.1、5.2、6)
UserDetails對象返回后,authenticate方法會默認(rèn)通過PasswordEncoder比對UserDetails與Authentication的密碼是否相同。因為UserDetails是通過自定義實現(xiàn)類從數(shù)據(jù)庫中查詢出的user對象,而Authentication相當(dāng)于是用戶輸入的用戶名和密碼,也就可以理解為通過前面自定義實現(xiàn)類利用用戶名查詢到用戶后,再看這個用戶的密碼是否正確。如果用戶名或密碼不正確,authenticate將會為空,則拋出異常信息。(對應(yīng)認(rèn)證流程的7)
由于這里的登錄流程不涉及8,9,10,所以不再敘述。
在剩下的代碼中我們利用用userId生成了jwt的令牌token,將其存入Redis中并返回token給前端。

登出流程
編寫過濾器
除login、register請求外的所有請求都需要攜帶token才能訪問,因此需要設(shè)計token攔截器代碼,如下。
對于不攜帶token的請求(如登錄/注冊)直接放行;對于攜帶token的請求先判斷該用戶是否登錄,即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è)置在過濾器UsernamePasswordAuthenticationFilter的前面。

編寫實現(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中存儲的值,即登出成功。
授權(quán)流程
確保實現(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對象
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);
這樣非登錄/注冊請求都會被設(shè)置授權(quán)信息。
為對應(yīng)接口添加注解@PreAuthorize,就會檢驗該請求是否存在相關(guān)請求。

完整代碼
config類
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Resource
private AccessDeniedHandlerImpl accessDeniedHandler;
@Resource
private AuthenticationEntryPointImpl authenticationEntryPoint;
@Bean
public PasswordEncoder passwordEncoder() {
// 實例化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();
// 添加過濾器
http
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 配置異常處理器
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
// 允許跨域
http.cors();
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
// 配置身份驗證管理器
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錯誤狀態(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的實現(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對象
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實現(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ù)庫中不存在用戶
if (Objects.isNull(user)) {
throw new RuntimeException("該手機號未注冊");
}
// 根據(jù)用戶查詢權(quán)限信息 添加到LoginUser中
List<String> list = menuMapper.selectPermsByUserPhone(user.getUserPhone());
// 封裝成UserDetails對象返回
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)證,校驗手機號和密碼是否正確
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserPhone(), user.getUserPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//認(rèn)證失敗給出提示
if (Objects.isNull(authenticate)) {
throw new RuntimeException("用戶名或密碼錯誤");
}
//認(rèn)證通過,生成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();
// 判斷是否存在相同手機號
User dataUser = lambdaQuery()
.eq(User::getUserPhone, user.getUserPhone()).one();
if (!Objects.isNull(dataUser)) {
return Result.fail("該手機號已注冊過用戶,請勿重復(fù)注冊");
}
// 密碼加密
user.setUserPassword(passwordEncoder
.encode(user.getUserPassword()));
// 將用戶及對應(yīng)角色存入數(shù)據(jù)庫
save(user);
userMapper.register(user.getUserPhone(), roleId);
return Result.ok("注冊成功");
}
}
utils類
public class JwtUtil {
// token失效:24小時
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;
}
/**
* 驗證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對象
*/
public static String parseJwt(String jwtToken) {
//驗證token
if (checkToken(jwtToken)) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken).getBody();
return claims.get("userId").toString();
}else {
throw new RuntimeException("超時或不合法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實現(xiàn)用戶認(rèn)證授權(quán)的文章就介紹到這了,更多相關(guān)SpringSecurity+Redis+Jwt認(rèn)證授權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 如何使用jwt+redis實現(xiàn)單點登錄
- 使用Redis實現(xiàn)JWT令牌主動失效機制
- Shiro整合Springboot和redis,jwt過程中的錯誤shiroFilterChainDefinition問題
- jwt+redis實現(xiàn)登錄認(rèn)證的示例代碼
- 基于 Redis 的 JWT令牌失效處理方案(實現(xiàn)步驟)
- springboot+springsecurity+mybatis+JWT+Redis?實現(xiàn)前后端離實戰(zhàn)教程
- SpringBoot整合SpringSecurity和JWT和Redis實現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫登錄認(rèn)證的實現(xiàn)
- java實現(xiàn)認(rèn)證與授權(quán)的jwt與token+redis,哪種方案更好用?
相關(guān)文章
Java實現(xiàn)在Word中嵌入多媒體(視頻、音頻)文件
這篇文章主要介紹了Java如何實現(xiàn)在Word中嵌入多媒體(視頻、音頻)文件,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)java有一定的幫助,感興趣的同學(xué)可以了解一下2021-12-12
Spring MVC簡介_動力節(jié)點Java學(xué)院整理
Spring MVC屬于SpringFrameWork的后續(xù)產(chǎn)品,已經(jīng)融合在Spring Web Flow里面。今天先從寫一個Spring MVC的HelloWorld開始,讓我們看看如何搭建起一個Spring mvc的環(huán)境并運行程序,感興趣的朋友一起學(xué)習(xí)吧2017-08-08
Java正則表達(dá)式_動力節(jié)點Java學(xué)院整理
什么是正則表達(dá)式,正則表達(dá)式的作用是什么?這篇文章主要為大家詳細(xì)介紹了Java正則表達(dá)式的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05

