Spring Security校验token
专注写bug 人气:0前言
之前采取项目中嵌套html
页面,实现基本的登录校验
、权限校验
、登出操作
、记住我
等功能试下。
但是,现在的开发基本都是前后分离
样式,后端并不需要配置登录页
的操作。
如何才能做到前后分离
,同时也能支持登录
和token
校验呢,本篇博客详细说明。
token配置
本次token
生成采取jwt
的方式。
引入JWT依赖文件
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>
配置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 ==="); // 返回请求端 Map<String,Object> resultMap = 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 { Map<String,Object> resultMap = 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、返回请求端 Map<String,Object> resultMap = 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、数据库、缓存等 // 返回成功 Map<String,Object> resultMap = 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); // 数据库获取权限信息 // 本次模拟 List<String> permissionValueList = Arrays.asList("admin","select"); Collection<GrantedAuthority> 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查询数据库获取用户信息 QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.eq("username",userName); User user = userMapper.selectOne(userQueryWrapper); // 判断用户是否存在 if(user == null){ throw new UsernameNotFoundException("账户信息不存在!"); } // 存在对应的用户信息,则将其封装,丢给security自己去解析 log.info("user:{}",user); // 权限暂时不查数据库 List<String> 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 List<String> permissionValueList; public SecurityUser() { } public SecurityUser(User user) { if (user != null) { this.user = user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> 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<User> { }
配置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
加载全部内容