前言
之前采取项目中嵌套html
页面,实现基本的登录校验
、权限校验
、登出操作
、记住我
等功能试下。
但是,现在的开发基本都是前后分离
样式,后端并不需要配置登录页
的操作。
如何才能做到前后分离
,同时也能支持登录
和token
校验呢,本篇博客详细说明。
token配置
本次token
生成采取jwt
的方式。
引入JWT依赖文件
com.auth0 java-jwt 3.10.3
配置token管理器类
自定一个Token生成和从token中解析用户名的一个类,并交给Spring管理。
package security.config; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import org.springframework.stereotype.Component; import java.util.Calendar; import java.util.HashMap; @Component public class TokenJwtManager { // 设置token时间 private int tokenEcpiration = 24*60*60*1000; // 毫秒 24h // 编码密钥 private String tokenSignKey = "123456"; // 1、根据用户名生成token public String createToken(String userName){ Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.SECOND, tokenEcpiration); String userName1 = JWT.create() .withHeader(new HashMap<>()) .withClaim("userName", userName) .withExpiresAt(calendar.getTime()) // 过期时间 .sign(Algorithm.HMAC256(tokenSignKey));// 签名 return userName1; } // 2、根据token得到用户名信息 public String getUserName(String token){ JWTVerifier build = JWT.require(Algorithm.HMAC256(tokenSignKey)).build(); DecodedJWT verify = build.verify(token); Claim userName = verify.getClaim("userName"); return userName.asString(); public static void main(String[] args) { String ss = new TokenJwtManager().createToken("1111111"); System.out.println(ss); System.out.println(new TokenJwtManager().getUserName(ss)); }
security 配置
配置未登录处理类
package security.config.handler; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * 未登录 */ @Component @Slf4j public class MyUnAuthEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { log.info("======= commence ==="); // 返回请求端 MapresultMap = new HashMap<>(); // 保存数据 resultMap.put("code","10000"); resultMap.put("msg","当前账户未登录"); resultMap.put("data",new HashMap<>()); // 设置返回消息类型 httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8"); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType("application/json;charset=UTF-8"); // 返回给请求端 PrintWriter writer = httpServletResponse.getWriter(); writer.write(resultMap.toString()); writer.close(); } }
配置无权限处理类
package security.config.handler; 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.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * 无权访问配置(前后分离) */ @Component // 交给spring管理 public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { MapresultMap = new HashMap<>(); // 保存数据 resultMap.put("code","403"); resultMap.put("msg","无权访问"); resultMap.put("data",null); // 设置返回消息类型 httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8"); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType("application/json;charset=UTF-8"); // 返回给请求端 PrintWriter writer = httpServletResponse.getWriter(); writer.write(resultMap.toString()); writer.flush(); writer.close(); } }
配置登出操作处理类
package security.config.handler; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.stereotype.Component; import security.config.TokenJwtManager; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * 登出 */ @Component @Slf4j public class MyLogoutHandler implements LogoutHandler { @Autowired private TokenJwtManager tokenJwtManager; // public MyLogoutHandler(TokenJwtManager tokenJwtManager) { // this.tokenJwtManager = tokenJwtManager; // } @Override public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) { // 1、从header中获取token String token = httpServletRequest.getHeader("token"); log.info("token信息为 {}",token); String userName = tokenJwtManager.getUserName(token); log.info("从token获取userName信息为 {}",token); // redis 移除登录信息等逻辑 // xxxxx // 2、返回请求端 MapresultMap = new HashMap<>(); // 保存数据 resultMap.put("code","200"); resultMap.put("msg",userName+"登录成功"); resultMap.put("data",new HashMap<>()); // 设置返回消息类型 httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8"); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType("application/json;charset=UTF-8"); // 返回给请求端 PrintWriter writer = null; try { writer = httpServletResponse.getWriter(); writer.write(resultMap.toString()); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } }
配置token认证过滤器
package security.filter; import com.fasterxml.jackson.databind.ObjectMapper; 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.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Component; import security.config.TokenJwtManager; import security.vo.SecurityUser; import security.vo.User; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; // 这里交给spring管理会报错 @Slf4j public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { private TokenJwtManager tokenJwtManager; private AuthenticationManager authenticationManager; public TokenLoginFilter(TokenJwtManager tokenJwtManager, AuthenticationManager authenticationManager) { this.tokenJwtManager = tokenJwtManager; this.authenticationManager = authenticationManager; this.setPostOnly(false); // 关闭登录只允许 post // 设置登陆路径,并且post请求 this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/user/login","POST")); } // 1、获取登录页传递来的账户和密码信息 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){ log.info("==== attemptAuthentication ======"); String userName = request.getParameter("userName"); String pwd = request.getParameter("passWord"); log.info("userName:{},pwd:{}",userName,pwd); return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userName, pwd,new ArrayList<>())); // 2、认证成功调用 @Autowired protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { log.info("==== successfulAuthentication ======"); // 认证成功之后,获取认证后的用户基本信息 SecurityUser securityUser = (SecurityUser) authResult.getPrincipal(); // 根据用户名生成对应的token String token = tokenJwtManager.createToken(securityUser.getUsername()); // token信息存于redis、数据库、缓存等 // 返回成功 MapresultMap = new HashMap<>(); // 保存数据 resultMap.put("code","200"); resultMap.put("msg","登录成功"); resultMap.put("data",token); // 设置返回消息类型 response.setHeader("Content-type", "text/html;charset=UTF-8"); response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=UTF-8"); // 返回给请求端 PrintWriter writer = response.getWriter(); writer.write(resultMap.toString()); writer.flush(); writer.close(); // 3、认证失败调用的方法 protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) log.info("==== unsuccessfulAuthentication ======"); resultMap.put("code","500"); resultMap.put("msg","登录验证失败"); resultMap.put("data",new HashMap<>()); }
配置token权限校验过滤器
package security.filter; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.stereotype.Component; import security.config.TokenJwtManager; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; /** * token 校验 */ @Slf4j //@Component // 交给 spring 会报错 public class TokenAuthFilter extends BasicAuthenticationFilter { private TokenJwtManager tokenJwtManager; public TokenAuthFilter(AuthenticationManager authenticationManager, TokenJwtManager tokenJwtManager) { super(authenticationManager); this.tokenJwtManager = tokenJwtManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("==== doFilterInternal ========== token校验"); //获取当前认证成功用户权限信息 UsernamePasswordAuthenticationToken authRequest = getAuthentication(request); if(authRequest != null){ // 有权限,则放入权限上下文中 SecurityContextHolder.getContext().setAuthentication(authRequest); } // 执行下一个 filter 过滤器链 chain.doFilter(request,response); private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { log.info("==== getAuthentication ====="); //从header获取token String token = request.getHeader("token"); log.info("token:{}",token); if(token != null) { //从token获取用户名 String username = tokenJwtManager.getUserName(token); log.info("解析token获取userName为:{}",username); // 数据库获取权限信息 // 本次模拟 ListpermissionValueList = Arrays.asList("admin","select"); Collection authority = new ArrayList<>(); for(String permissionValue : permissionValueList) { SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue); authority.add(auth); } return new UsernamePasswordAuthenticationToken(username,token,authority); return null; }
自定义加密类
package security.config; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import security.utils.Md5Utils; /** * 密码加密、比对 */ @Component // bean @Slf4j public class DefaultPwdEndoder implements PasswordEncoder { /** * 加密 * @param charSequence * @return */ @Override public String encode(CharSequence charSequence) { log.info("==== encode ===="); log.info("charSequence 为 {}",charSequence); log.info("charSequence md5为 {}",Md5Utils.md5(charSequence.toString())); return Md5Utils.md5(charSequence.toString()); } * 进行密码比对 * @param charSequence 不加密 * @param encodePwd 加密 public boolean matches(CharSequence charSequence, String encodePwd) { log.info("==== matches ===="); log.info("charSequence:{}",charSequence); log.info("charSequenceMd5:{}",Md5Utils.md5(charSequence.toString())); log.info("encodePwd:{}",encodePwd); return encodePwd.equalsIgnoreCase(Md5Utils.md5(charSequence.toString())); }
配置UserDetailService
package security.service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 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.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 security.mapper.UserMapper; import security.vo.SecurityUser; import security.vo.User; import java.util.Arrays; import java.util.List; /** * security 登录信息和权限获取类 */ @Service("userDetailsService") @Slf4j public class UserDetailService implements UserDetailsService { // 注入Usermapper @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { log.info("====== loadUserByUsername ======"); // 通过username查询数据库获取用户信息 QueryWrapperuserQueryWrapper = new QueryWrapper<>(); userQueryWrapper.eq("username",userName); User user = userMapper.selectOne(userQueryWrapper); // 判断用户是否存在 if(user == null){ throw new UsernameNotFoundException("账户信息不存在!"); } // 存在对应的用户信息,则将其封装,丢给security自己去解析 log.info("user:{}",user); // 权限暂时不查数据库 List admin = Arrays.asList("ROLE_user,ROLE_admin,admin"); // 将数据封装给 SecurityUser ,因为 SecurityUser 是 UserDetails 的子类 SecurityUser securityUser = new SecurityUser(); securityUser.setPermissionValueList(admin); securityUser.setUser(user); log.info("securityUser:{}",securityUser.toString()); return securityUser; } }
配置数据库User对象映射类
package security.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -5461108964440966122L; private Integer id; private String username; private String password; private Integer enabled; private Integer locked; }
配置UserDetailService使用的SecurityUser类
package security.vo; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * UserDetailService 使用该类,该类必须是 UserDetails 的子类 */ @Data public class SecurityUser implements UserDetails { // 登录用户的基本信息 private User user; //当前权限 private ListpermissionValueList; public SecurityUser() { } public SecurityUser(User user) { if (user != null) { this.user = user; } @Override public Collection extends GrantedAuthority> getAuthorities() { Collection authorities = new ArrayList<>(); permissionValueList.forEach(permission ->{ if(!StringUtils.isEmpty(permission)){ SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission); authorities.add(authority); } }); return authorities; public String getPassword() { return user.getPassword(); public String getUsername() { return user.getUsername(); public boolean isAccountNonExpired() { return true; public boolean isAccountNonLocked() { public boolean isCredentialsNonExpired() { public boolean isEnabled() { }
配置mybatis-plus
首先,需要配置application.properties
数据库连接源。
spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://106.55.137.66:3306/security?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver server.port=80
其次,需要配置Mapper类,查询数据库获取基本数据信息。
package security.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.springframework.stereotype.Repository; import security.vo.User; @Repository public interface UserMapper extends BaseMapper{ }
配置security配置类
package security.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; 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.WebSecurityConfigurerAdapter; import security.config.handler.*; import security.filter.TokenAuthFilter; import security.filter.TokenLoginFilter; import security.service.UserDetailService; /** * security 配置类 */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) // 方法增加权限 public class MyTokenSecurityConfig extends WebSecurityConfigurerAdapter { // 将 UserDetailService 注入,使其去查询数据库 @Autowired private UserDetailService userDetailsService; // token 生成器 @Autowired private TokenJwtManager tokenManager; // 自定义密码加密解密 @Autowired private DefaultPwdEndoder defaultPwdEndoder; // 未登录handler @Autowired private MyUnAuthEntryPoint myUnAuthEntryPoint; // 无权限 @Autowired private MyAccessDeniedHandler myAccessDeniedHandler; // 登出handler处理 @Autowired private MyLogoutHandler myLogoutHandler; // 登录失败 @Autowired private LoginFailedHandler loginFailedHandler; // 登录成功 @Autowired private LoginSuccessHandler loginSuccessHandler; /** * 登录时,从数据库获取基本信息和权限信息 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 设置 userDetailsService 和 密码解析 auth.userDetailsService(userDetailsService).passwordEncoder(defaultPwdEndoder); } /** * 配置访问过滤 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() .authenticationEntryPoint(myUnAuthEntryPoint) // 未登录 handler .accessDeniedHandler(myAccessDeniedHandler) // 无权限 .and().csrf().disable() // 关闭 csrf 跨域请求 .formLogin() .loginProcessingUrl("/user/login") // 设定登录请求接口 .usernameParameter("userName") .passwordParameter("passWord") //.successHandler(loginSuccessHandler) // 因为有了 TokenLoginFilter 配置过滤器,此处配置没用 //.failureHandler(loginFailedHandler) // 因为有了 TokenLoginFilter 配置过滤器,此处配置没用 .permitAll() .and() .authorizeRequests() // 请求设置 .antMatchers("/test").permitAll() // 配置不需要认证的接口 .anyRequest().authenticated() // 任何请求都需要认证 .and() .logout() // logout设定 .logoutUrl("/logouts") //退出请求 /logouts 未定义,交给自定义handler实现功能 .addLogoutHandler(myLogoutHandler) // 登出 myLogoutHandler 处理 .and() .addFilter(new TokenLoginFilter(tokenManager,authenticationManager())) // 认证交给 自定义 TokenLoginFilter 实现 .addFilter(new TokenAuthFilter(authenticationManager(),tokenManager)) .httpBasic(); } /** * 配置不需要验证的访问路径 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { //web.ignoring().antMatchers("/test","/user/login"); web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**"); } }
配置几个测试接口
package security.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @RequestMapping("/test") public String test(){ return "不需要认证就能访问"; } }
package security.controller; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @RequestMapping("/user/test1") @PreAuthorize("hasAnyAuthority('admin','user')") public String test1(){ return "需要认证的 /user/test1"; } @RequestMapping("/user/test2") @PreAuthorize("hasAnyAuthority('test')") public String test2(){ return "需要认证的 /user/test2"; }
Md5加密工具类
package security.utils; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; /** * 加密工具类 */ public class Md5Utils { public static String md5(String str) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); byte b[] = md.digest(); str = byteToStr(b); } catch (Exception e) { e.printStackTrace(); } return str; } public static String byteToStr(byte[] b){ int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < b.length; offset++) { i = b[offset]; //System.out.println(i); if (i < 0) i += 256; if (i < 16) buf.append("0"); buf.append(Integer.toHexString(i)); return buf.toString(); /** * 传入文本内容,返回 SHA-256 串 * * @param strText * @return */ public static String SHA256(final String strText) { return SHA(strText, "SHA-256"); public static String SHA1(final String strText) return SHA(strText, "SHA-1"); * 传入文本内容,返回 SHA-512 串 public static String SHA512(final String strText) return SHA(strText, "SHA-512"); * 字符串 SHA 加密 private static String SHA(final String strText, final String strType) // 返回值 String strResult = null; // 是否是有效字符串 if (strText != null && strText.length() > 0) { try { // SHA 加密开始 MessageDigest messageDigest = MessageDigest.getInstance(strType); // 传入要加密的字符串 messageDigest.update(strText.getBytes("utf-8")); // 得到 byte 类型的结果 byte byteBuffer[] = messageDigest.digest(); strResult = byteToStr(byteBuffer); } catch (NoSuchAlgorithmException e) e.printStackTrace(); }catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block return strResult; public static String base64(String str){ String baseStr = null; Base64.Encoder encoder = Base64.getEncoder(); byte[] textByte; textByte = str.getBytes("UTF-8"); baseStr = encoder.encodeToString(textByte); } catch (UnsupportedEncodingException e) { return baseStr; public static void main(String[] args) { String password = "bunana1"; System.out.println(md5(password)); //String base64 = base64(sha512); //System.out.println(base64); //String pwd1 = md5(base64); //System.out.println(pwd1); }
测试
测试采取ApiPost 工具
,让测试更接近前后分离。
首先测试登录
Post
localhost/user/login
账号密码有一个不对时。
正确的账号密码
测试存在权限的接口
localhost/user/test1
测试不存在权限的接口
localhost/user/test2
测试登出
localhost/logouts
测试不需要权限的接口
localhost/test
数据库sql脚本
CREATE TABLE `user` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, -- 主键 `username` varchar(255) DEFAULT NULL, -- 用户名 `password` varchar(255) DEFAULT NULL, -- 用户密码 `enabled` tinyint(1) DEFAULT '1', -- 是否启用 1-启用 0-未启用 `locked` tinyint(1) DEFAULT '0', -- 是否被锁 1-已锁 0-未锁 PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据为:
insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS","1babad058e03c5296a94a5a8d7d6dd8a",1,0); -- bunana 的md5 值 insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS2","0b13310f8db2dc22e7ddd0cdc5f0a61a",1,0); -- bunana1 的md5 值 insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS3","b3fbcd9c9d97e47f263a19a0e01efc7d",1,0); -- bunana2 的md5 值
代码下载
springboot-security-10-qianhou
gitee 代码下载地址
到此这篇关于Spring Security前后分离校验token的文章就介绍到这了,更多相关Spring Security校验token内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!