赞
踩
目录
三:认证(UsernamePasswordAuthenticationFiter)
四:授权(FilterSecurityInterceptor)
五:失败处理(ExceptionTranslationFilter)
1.Swagger文档地址: http://localhost:8080/doc.html
1、创建SpringBoot项目
- <!--spring boot-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
2、整合MyBatis
- <!-- mybatis -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- </dependency>
- <!-- mysql -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
3、引入SpringSecurity
- <!--spring security 启动器-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- <!--jwt-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.0</version>
- </dependency>
4、创建测试接口http://localhost:8080/hello,进行测试。
当引入security成功之后,在登录测试接口时就会跳转到默认的登录页http://localhost:8080/login。
(默认用户名:user,密码:控制台输出)
1、登录校验流程
2、SpringSecurity简单过滤器链(完整的过滤器大概是14个),前后端分离一般用jwt,前后端不分离一般采用session
1、登录流程:
①、登录、自定义登录接口:
调用ProviderManager的方法进行认证,如果认证通过生成jwt和把用户信息存到redis中。
- // 认证 实现代码类
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUserName(), sysUser.getPassword());
- Authentication authenticate = authenticationManager.authenticate(authenticationToken);
- // 认证通过 生成jwt
- LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
- String userId = loginUser.getSysUser().getId().toString();
- String jwt = JwtUtil.createJWT(userId);// 使用userId生成 jwt
- Map<String, String> map = new HashMap<>(1);
- map.put("token", jwt);
- // 认证通过 存入 redis
- redisCache.setCacheObject("login:" + userId, loginUser);
-
-
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- //认证 配置代码
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- // ......
- }
- }
②、登录、自定义UserDetailsService:
实现到db中查询用户信息,因为原接口是从内存中查询的。
- //重写 UserDetailsService 的 loadUserByUsername 方法
- public class UserDetailsServiceImpl implements UserDetailsService{
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- //......
- }
- }
-
- //重写UserDetails返回的用户信息
- public class LoginUser implements UserDetails {
- //......
- }
2、token认证:
①、校验、定义jwt认证过滤器:
获取toekn、解析token获取其中的userId、从redis中获取用户信息、使用SecurityContextHolder.getContext().setAuthentication()方法存储该对象,这样其他过滤器会通过SecurityContextHolder来获取当前用户信息。
- // token认证过滤器
- @Component
- public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- // 省略...... 拦截到 token不合法等情况
- // 将 Authentication对象存入 SecurityContextHolder
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- //放行
- filterChain.doFilter(request, response);
- }
- }
1、将用户的权限信息封装到Authentcation当中,存到SecurityContextHolder中。
- @Service
- public class UserDetailsServiceImpl implements UserDetailsService {
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- // 省略 ......
- // 根据用户id查询权限字符串集合
- List<String> list = sysMenuDao.selectPermsByUserId(userInfo.getId());
- return new LoginUser(userInfo, list);
- }
- }
-
-
- public class LoginUser implements UserDetails {
- // ......
- @JSONField(serialize = false) //fastjson注解,表示此属性不会被序列化到redis当中
- private List<SimpleGrantedAuthority> authorities;
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- // 权限为空的时候才往遍历,不为空直接返回
- if (authorities != null) {
- return authorities;
- }
- //把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象
- authorities = new ArrayList<>();
- for (String permission : permissions) {
- SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
- authorities.add(authority);
- }
- return authorities;
- }
-
- // ......
- }
-
-
- // token过滤器中( JwtAuthenticationTokenFilter )
- // 将Authentication对象(用户信息、已认证状态、权限信息)存入 SecurityContextHolder
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
2、从SecurityContextHolder中获取Authentcation对象,再获取其中的权限信息。
- // token过滤器中( JwtAuthenticationTokenFilter )
- String redisKey = "login:" + userId;
- LoginUser loginUser = redisCache.getCacheObject(redisKey);// 从redis中获取用户信息
3、设置每个资源(方法)所需要的权限(注解形式设置权限)
RBAC(基于角色的权限控制)
- //SecurityConfig 配置类开启权限注解功能
- @EnableGlobalMethodSecurity(prePostEnabled = true)
-
- /***** 对应资源(方法)上授权 *****/
- //常用,该用户在数据库中有 system:dept:list 这个权限标识才能访问
- @PreAuthorize("hasAuthority('system:dept:list')")
- @PreAuthorize("hasAnyAuthority()")
- @PreAuthorize("hasAnyRole()")
- @PreAuthorize("hasAnyRole()")
- @PreAuthorize("hasPermission()")
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- // ......
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.exceptionHandling() // 配置异常处理器
- .authenticationEntryPoint(authenticationEntryPoint)// 认证失败
- .accessDeniedHandler(accessDeniedHandler); // 授权失败
- }
- // ......
- }
1、认证失败处理器
- @Component
- public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
- // ......
- }
2、授权失败处理器
- @Component
- public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
- // ......
- }
同时处理 springboot跨域 和 springsecurity跨域
- // springboot 跨域配置类
- @Configuration
- public class CorsConfig implements WebMvcConfigurer {
- // ......
- }
-
-
- // SecurityConfig配置类
- http.cors();// spring security 允许跨域
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.6.6</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.example</groupId>
- <artifactId>boot_security</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>boot_security</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <!--spring boot-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!--lombok-->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <!--spring security 启动器-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- <!--redis-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!--fastjson-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.33</version>
- </dependency>
- <!--jwt-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.0</version>
- </dependency>
- <!-- mybatis -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- </dependency>
- <!-- mysql -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- </dependencies>
- <build>
- <!--mybatis加载配置文件-->
- <resources>
- <resource>
- <directory>src/main/resources</directory>
- </resource>
- <resource>
- <directory>src/main/java</directory>
- <includes>
- <include>**/*.xml</include>
- </includes>
- </resource>
- </resources>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
- server:
- port: 8080
-
- spring:
- # 数据源配置
- datasource:
- url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
- username: root
- password: root
- driver-class-name: com.mysql.cj.jdbc.Driver
- # redis配置
- redis:
- host: localhost
- port: 6379
- password: 123456
-
- # mybatis配置
- mybatis:
- # 配置SQL映射文件路径
- # mapper-locations: classpath:/mapper/*.xml
- mapper-locations: classpath*:com/example/boot_security/**/mapper/*.xml
- # 驼峰命名
- configuration:
- mapUnderscoreToCamelCase: true
- /**
- * 授权失败
- */
- @Component
- public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
- @Override
- public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
- ResponseResult result = new ResponseResult(403, "您的权限不足!");
- String json = JSON.toJSONString(result);
- // 将字符串渲染到客户端
- WebUtils.renderString(response, json);
- }
- }
- /**
- * 认证失败
- */
- @Component
- public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
- @Override
- public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
- ResponseResult result = new ResponseResult(401, "认证失败,请重新登录!");
- String json = JSON.toJSONString(result);
- // 将字符串渲染到客户端
- WebUtils.renderString(response, json);
- }
- }
- /**
- * springboot 跨域配置类
- */
- @Configuration
- public class CorsConfig implements WebMvcConfigurer {
-
- @Override
- public void addCorsMappings(CorsRegistry registry) {
- // 设置允许跨域的路径
- registry.addMapping("/**")
- // 设置允许跨域请求的域名
- .allowedOriginPatterns("*")
- // 是否允许cookie
- .allowCredentials(true)
- // 设置允许的请求方式
- .allowedMethods("GET", "POST", "DELETE", "PUT")
- // 设置允许的header属性
- .allowedHeaders("*")
- // 跨域允许时间
- .maxAge(3600);
- }
- }
- /**
- * Redis使用FastJson序列化
- */
- public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
-
- public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
- private Class<T> clazz;
-
- static {
- ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
- }
-
- public FastJsonRedisSerializer(Class<T> clazz) {
- super();
- this.clazz = clazz;
- }
-
- @Override
- public byte[] serialize(T t) throws SerializationException {
- if (t == null) {
- return new byte[0];
- }
- return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
- }
-
- @Override
- public T deserialize(byte[] bytes) throws SerializationException {
- if (bytes == null || bytes.length <= 0) {
- return null;
- }
- String str = new String(bytes, DEFAULT_CHARSET);
-
- return JSON.parseObject(str, clazz);
- }
-
-
- protected JavaType getJavaType(Class<?> clazz) {
- return TypeFactory.defaultInstance().constructType(clazz);
- }
- }
- /**
- * token认证过滤器
- * 作用:解析请求头中的token。并验证合法性
- * 继承 OncePerRequestFilter 保证请求经过过滤器一次
- */
- @Component
- public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
-
- @Resource
- private RedisCache redisCache;
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- String token = request.getHeader("token");
- // 没有token
- if (!StringUtils.hasText(token)) {
- filterChain.doFilter(request, response);//放行,因为后面的会抛出相应的异常
- return;
- }
-
- // 非法token
- String userId;
- try {
- Claims claims = JwtUtil.parseJWT(token);
- userId = claims.getSubject();
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException("非法token!");
- }
-
-
- String redisKey = "login:" + userId;
- LoginUser loginUser = redisCache.getCacheObject(redisKey);// 从redis中获取用户信息
-
- // redis中用户不存在
- if (Objects.isNull(loginUser)) {
- throw new RuntimeException("redis中用户不存在!");
- }
-
- // 将Authentication对象(用户信息、已认证状态、权限信息)存入 SecurityContextHolder
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- //放行
- filterChain.doFilter(request, response);
- }
- }
- /**
- * Jwt工具类
- */
- public class JwtUtil {
-
- /**
- * 有效期为
- * 60 * 60 *1000 一个小时
- */
- public static final Long JWT_TTL = 60 * 60 * 1000L;
- /**
- * 设置秘钥明文
- */
- public static final String JWT_KEY = "sangeng";
-
-
- public static String getUUID() {
- String token = UUID.randomUUID().toString().replaceAll("-", "");
- return token;
- }
-
- /**
- * 生成 jtw
- *
- * @param subject token中要存放的数据(json格式)
- * @return
- */
- public static String createJWT(String subject) {
- // 设置过期时间 空
- JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
- return builder.compact();
- }
-
- /**
- * 生成 jtw
- *
- * @param subject token中要存放的数据(json格式)
- * @param ttlMillis token超时时间
- * @return
- */
- public static String createJWT(String subject, Long ttlMillis) {
- // 设置过期时间
- JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());
- return builder.compact();
- }
-
- private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
- SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
- SecretKey secretKey = generalKey();
- long nowMillis = System.currentTimeMillis();
- Date now = new Date(nowMillis);
- if (ttlMillis == null) {
- ttlMillis = JwtUtil.JWT_TTL;
- }
- long expMillis = nowMillis + ttlMillis;
- Date expDate = new Date(expMillis);
- return Jwts.builder()
- // 唯一的ID
- .setId(uuid)
- // 主题 可以是JSON数据
- .setSubject(subject)
- // 签发者
- .setIssuer("sg")
- // 签发时间
- .setIssuedAt(now)
- // 使用 HS256 对称加密算法签名, 第二个参数为秘钥
- .signWith(signatureAlgorithm, secretKey)
- .setExpiration(expDate);
- }
-
- /**
- * 创建 token
- *
- * @param id
- * @param subject
- * @param ttlMillis
- * @return
- */
- public static String createJWT(String id, String subject, Long ttlMillis) {
- // 设置过期时间
- JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);
- return builder.compact();
- }
-
- /**
- * 生成加密后的秘钥 secretKey
- *
- * @return
- */
- public static SecretKey generalKey() {
- byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
- SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
- return key;
- }
-
- /**
- * 解析
- *
- * @param jwt
- * @return
- * @throws Exception
- */
- public static Claims parseJWT(String jwt) throws Exception {
- SecretKey secretKey = generalKey();
- return Jwts.parser()
- .setSigningKey(secretKey)
- .parseClaimsJws(jwt)
- .getBody();
- }
-
- //测试方法
- public static void main(String[] args) throws Exception {
- //JWT加密
- String jwt = createJWT("123456");
- System.out.println(jwt);
- //JWT解密 时间过期会报错,须重新生成再解析
- Claims claims = parseJWT("密文");
- String subject = claims.getSubject();
- System.out.println(subject);
- }
- }
- /**
- * redis工具类
- */
- @SuppressWarnings(value = {"unchecked", "rawtypes"})
- @Component
- public class RedisCache {
- @Autowired
- public RedisTemplate redisTemplate;
-
- /**
- * 缓存基本的对象,Integer、String、实体类等
- *
- * @param key 缓存的键值
- * @param value 缓存的值
- */
- public <T> void setCacheObject(final String key, final T value) {
- redisTemplate.opsForValue().set(key, value);
- }
-
- /**
- * 缓存基本的对象,Integer、String、实体类等
- *
- * @param key 缓存的键值
- * @param value 缓存的值
- * @param timeout 时间
- * @param timeUnit 时间颗粒度
- */
- public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
- redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
- }
-
- /**
- * 设置有效时间
- *
- * @param key Redis键
- * @param timeout 超时时间
- * @return true=设置成功;false=设置失败
- */
- public boolean expire(final String key, final long timeout) {
- return expire(key, timeout, TimeUnit.SECONDS);
- }
-
- /**
- * 设置有效时间
- *
- * @param key Redis键
- * @param timeout 超时时间
- * @param unit 时间单位
- * @return true=设置成功;false=设置失败
- */
- public boolean expire(final String key, final long timeout, final TimeUnit unit) {
- return redisTemplate.expire(key, timeout, unit);
- }
-
- /**
- * 获得缓存的基本对象。
- *
- * @param key 缓存键值
- * @return 缓存键值对应的数据
- */
- public <T> T getCacheObject(final String key) {
- ValueOperations<String, T> operation = redisTemplate.opsForValue();
- return operation.get(key);
- }
-
- /**
- * 删除单个对象
- *
- * @param key
- */
- public boolean deleteObject(final String key) {
- return redisTemplate.delete(key);
- }
-
- /**
- * 删除集合对象
- *
- * @param collection 多个对象
- * @return
- */
- public long deleteObject(final Collection collection) {
- return redisTemplate.delete(collection);
- }
-
- /**
- * 缓存List数据
- *
- * @param key 缓存的键值
- * @param dataList 待缓存的List数据
- * @return 缓存的对象
- */
- public <T> long setCacheList(final String key, final List<T> dataList) {
- Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
- return count == null ? 0 : count;
- }
-
- /**
- * 获得缓存的list对象
- *
- * @param key 缓存的键值
- * @return 缓存键值对应的数据
- */
- public <T> List<T> getCacheList(final String key) {
- return redisTemplate.opsForList().range(key, 0, -1);
- }
-
- /**
- * 缓存Set
- *
- * @param key 缓存键值
- * @param dataSet 缓存的数据
- * @return 缓存数据的对象
- */
- public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
- BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
- Iterator<T> it = dataSet.iterator();
- while (it.hasNext()) {
- setOperation.add(it.next());
- }
- return setOperation;
- }
-
- /**
- * 获得缓存的set
- *
- * @param key
- * @return
- */
- public <T> Set<T> getCacheSet(final String key) {
- return redisTemplate.opsForSet().members(key);
- }
-
- /**
- * 缓存Map
- *
- * @param key
- * @param dataMap
- */
- public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
- if (dataMap != null) {
- redisTemplate.opsForHash().putAll(key, dataMap);
- }
- }
-
- /**
- * 获得缓存的Map
- *
- * @param key
- * @return
- */
- public <T> Map<String, T> getCacheMap(final String key) {
- return redisTemplate.opsForHash().entries(key);
- }
-
- /**
- * 往Hash中存入数据
- *
- * @param key Redis键
- * @param hKey Hash键
- * @param value 值
- */
- public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
- redisTemplate.opsForHash().put(key, hKey, value);
- }
-
- /**
- * 获取Hash中的数据
- *
- * @param key Redis键
- * @param hKey Hash键
- * @return Hash中的对象
- */
- public <T> T getCacheMapValue(final String key, final String hKey) {
- HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
- return opsForHash.get(key, hKey);
- }
-
- /**
- * 删除Hash中的数据
- *
- * @param key
- * @param hkey
- */
- public void delCacheMapValue(final String key, final String hkey) {
- HashOperations hashOperations = redisTemplate.opsForHash();
- hashOperations.delete(key, hkey);
- }
-
- /**
- * 获取多个Hash中的数据
- *
- * @param key Redis键
- * @param hKeys Hash键集合
- * @return Hash对象集合
- */
- public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
- return redisTemplate.opsForHash().multiGet(key, hKeys);
- }
-
- /**
- * 获得缓存的基本对象列表
- *
- * @param pattern 字符串前缀
- * @return 对象列表
- */
- public Collection<String> keys(final String pattern) {
- return redisTemplate.keys(pattern);
- }
- }
- /**
- * redis配置类
- * 避免存入redis中的key看上去乱码的现象
- */
- @Configuration
- public class RedisConfig {
-
- @Bean
- @SuppressWarnings(value = {"unchecked", "rawtypes"})
- public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
- RedisTemplate<Object, Object> template = new RedisTemplate<>();
- template.setConnectionFactory(connectionFactory);
-
- FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
-
- // 使用StringRedisSerializer来序列化和反序列化redis的key值
- template.setKeySerializer(new StringRedisSerializer());
- template.setValueSerializer(serializer);
-
- // Hash的key也采用StringRedisSerializer的序列化方式
- template.setHashKeySerializer(new StringRedisSerializer());
- template.setHashValueSerializer(serializer);
-
- template.afterPropertiesSet();
- return template;
- }
- }
- /**
- * SpringSecurity 核心配置类
- * prePostEnabled = true 开启注解权限认证功能
- */
- @Configuration
- @EnableGlobalMethodSecurity(prePostEnabled = true)//开启@PreAuthorize()注解权限功能
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- //认证过滤器
- @Autowired
- private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
-
- // 认证失败处理器
- @Autowired
- private AuthenticationEntryPointImpl authenticationEntryPoint;
-
- // 授权失败处理器
- @Autowired
- private AccessDeniedHandlerImpl accessDeniedHandler;
-
-
- /**
- * 密码机密处理器
- * <p>
- * 将BCryptPasswordEncoder对象注入到spring容器中,更换掉原来的 PasswordEncoder加密方式
- * 原PasswordEncoder密码格式为:{id}password。它会根据id去判断密码的加密方式。
- * 如果没替换原来的加密方式,数据库中想用明文密码做测试,将密码字段改为{noop}123456这样的格式
- */
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
- /**
- * 认证配置
- * anonymous():匿名访问:未登录可以访问,已登录不能访问
- * permitAll():有没有认证都能访问:登录或未登录都能访问
- * denyAll(): 拒绝
- * authenticated():认证之后才能访问
- * hasAuthority():包含权限
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 关闭csrf(前后端分离项目要关闭此功能)
- .csrf().disable()
- // 禁用session (前后端分离项目,不通过Session获取SecurityContext)
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- // 请求认证配置
- .authorizeRequests()
- // 允许匿名访问:未登录可以访问,已登录不能访问
- .antMatchers("/login").anonymous()
- // .antMatchers("/login").permitAll()// 登录或未登录都能访问
- // .antMatchers("/textMybatis").hasAuthority("system:dept:list22")
- // 任意用户,认证之后才可以访问(除上面外的)
- .anyRequest().authenticated();
-
- // 添加token过滤器
- http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
-
- // 配置异常处理器
- http.exceptionHandling()
- // 认证失败
- .authenticationEntryPoint(authenticationEntryPoint)
- // 授权失败
- .accessDeniedHandler(accessDeniedHandler);
-
- // spring security 允许跨域
- http.cors();
- }
-
- /**
- * 注入AuthenticationManager 进行用户认证
- */
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- }
- /**
- * 将字符串渲染到客户端
- * 向响应之中写入中聚类
- *
- * @author bing_ @create 2022/1/4-22:20
- */
- public class WebUtils {
- /**
- * 将字符串渲染到客户端
- *
- * @param response 渲染对象
- * @param string 待渲染的字符串
- * @return null
- */
- 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;
- }
- }
- /**
- * 测试接口 账户名:sg 密码:1111
- */
- @RestController
- @RequestMapping("/test")
- public class HelloController {
-
- @Autowired
- private SysUserDao sysUserDao;
-
- /**
- * 测试 springBoot
- */
- @GetMapping("/SpringBoot")
- @PreAuthorize("hasAuthority('system:dept:list')")//授权
- public String testSpringBoot(){
- return "测试 springBoot";
- }
-
- /**
- * 测试 mybatis
- */
- @GetMapping("/Mybatis")
- public SysUser textMybatis(){
- return sysUserDao.getUserInfo("sg");
- }
- }
1.LoginController
- /**
- * 一个坑:退出路径如果只有 "/logout" 会报403 ,原因不明
- * 解决方法:前面添加个前路径,或者换一个名就可以了
- */
- @RestController
- public class LoginController {
-
- @Autowired
- private LoginService loginService;
-
- /**
- * 登录
- */
- @PostMapping("/login")
- public ResponseResult login(@RequestBody SysUser sysUser) {
- return loginService.login(sysUser);
- }
-
- /**
- * 退出登录
- */
- @GetMapping("/user/logout")
- public ResponseResult logout() {
- return loginService.logout();
- }
- }
5.2.1.SysMenuDao
- public interface SysMenuDao {
- /**
- * 根据用户id查询权限字符串集合
- */
- List<String> selectPermsByUserId(Long userId);
- }
5.2.2.SysUserDao
- public interface SysUserDao {
- /**
- * 获取用户信息
- */
- @Select("select * from sys_user where user_name = #{username}")
- SysUser getUserInfo(String username);
- }
5.3.1.SysUser
- /**
- * 用户表(sys_user)实体表
- */
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class SysUser {
- private Long id;//主键
- private String userName;//用户名
- private String nickName;//昵称
- private String password;//密码
- private String status;//账号状态(0正常 1停用)
- private String email;//邮箱
- private String phonenumber;//手机号
- private String sex;//用户性别(0男,1女,2未知)
- private String avatar;//头像
- private String userType;//用户类型(0管理员,1普通用户)
- private Long createBy;//创建人的用户id
- private LocalDateTime createTime;//创建时间
- private Long updateBy;//更新人
- private LocalDateTime updateTime;//更新时间
- private Integer delFlag;//删除标志(0代表未删除,1代表已删除)
- }
5.3.2.LoginUser
- /**
- * 重写UserDetails返回的用户信息
- * SpringSecurity返回的用户信息实体类
- */
- @Data
- @NoArgsConstructor
- public class LoginUser implements UserDetails {
-
- private SysUser sysUser;//用户信息
-
- private List<String> permissions;//权限信息
-
- public LoginUser(SysUser sysUser, List<String> permissions) {
- this.sysUser = sysUser;
- this.permissions = permissions;
- }
-
- @JSONField(serialize = false) //fastjson注解,表示此属性不会被序列化,因为SimpleGrantedAuthority这个类型不能在redis中序列化
- private List<SimpleGrantedAuthority> authorities;
-
- /**
- * 获取权限信息
- */
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- // 权限为空的时候才往遍历,不为空直接返回
- if (authorities != null) {
- return authorities;
- }
- //把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象
- authorities = new ArrayList<>();
- for (String permission : permissions) {
- SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
- authorities.add(authority);
- }
- return authorities;
- }
-
- /**
- * 获取密码
- */
- @Override
- public String getPassword() {
- return sysUser.getPassword();
- }
-
- /**
- * 获取用户名
- */
- @Override
- public String getUsername() {
- return sysUser.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;
- }
- }
5.3.3. SysMenu
- /**
- * 菜单表(sys_menu)实体表
- */
- @Data
- public class SysMenu{
-
- private Long id;
- private String menuName;//菜单名
- private String path;//路由地址
- private String component;//组件路径
- private String visible;//菜单状态(0显示 1隐藏)
- private String status;//菜单状态(0正常 1停用)
- private String perms;//权限标识
- private String icon;//菜单图标
- private Long createBy;
- private LocalDateTime createTime;
- private Long updateBy;
- private LocalDateTime updateTime;
- private Integer delFlag;//是否删除(0未删除 1已删除)
- private String remark;//备注
- }
5.3.4.SysUser
- /**
- * 用户表(sys_user)实体表
- */
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class SysUser {
- private Long id;//主键
- private String userName;//用户名
- private String nickName;//昵称
- private String password;//密码
- private String status;//账号状态(0正常 1停用)
- private String email;//邮箱
- private String phonenumber;//手机号
- private String sex;//用户性别(0男,1女,2未知)
- private String avatar;//头像
- private String userType;//用户类型(0管理员,1普通用户)
- private Long createBy;//创建人的用户id
- private LocalDateTime createTime;//创建时间
- private Long updateBy;//更新人
- private LocalDateTime updateTime;//更新时间
- private Integer delFlag;//删除标志(0代表未删除,1代表已删除)
- }
5.4.1.SysMenuMapper.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.example.boot_security.sysSecurity.dao.SysMenuDao">
-
- <!--根据用户id查询权限字符串集合-->
- <select id="selectPermsByUserId" resultType="java.lang.String">
- SELECT DISTINCT m.perms
- FROM sys_user_role ur
- LEFT JOIN sys_role r ON ur.role_id = r.id
- LEFT JOIN sys_role_menu rm ON r.id = rm.role_id
- LEFT JOIN sys_menu m ON rm.menu_id = m.id
- WHERE ur.user_id = #{userId}
- </select>
- </mapper>
5.5.1.LoginService 接口
- public interface LoginService {
- /**
- * 登录
- */
- ResponseResult login(SysUser sysUser);
-
- /**
- * 退出登录
- */
- ResponseResult logout();
- }
5.5.2.SysUserService 接口
- public interface SysUserService {
-
- /**
- * 根据用户名获取用户信息
- */
- SysUser getUserInfo(String username);
- }
5.5.3.impl实现类
5.5.3.1.LoginServiceImpl
- @Service
- public class LoginServiceImpl implements LoginService {
-
- @Autowired
- private AuthenticationManager authenticationManager;
-
- @Autowired
- private RedisCache redisCache;
-
- @Override
- public ResponseResult login(SysUser sysUser) {
- // 认证
- UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUserName(), sysUser.getPassword());
- Authentication authenticate = authenticationManager.authenticate(authenticationToken);
- // 认证没通过
- if (Objects.isNull(authenticate)) {
- throw new RuntimeException("用户名或密码错误!");
- }
- // 认证通过 生成jwt
- LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
- String userId = loginUser.getSysUser().getId().toString();
- String jwt = JwtUtil.createJWT(userId);
- Map<String, String> map = new HashMap<>(1);
- map.put("token", jwt);
- // 认证通过 存入 redis
- redisCache.setCacheObject("login:" + userId, loginUser);
- return new ResponseResult(200, "登录成功", map);
- }
-
- @Override
- public ResponseResult logout() {
- LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- Long userId = loginUser.getSysUser().getId();
- // 清空redis
- redisCache.deleteObject("login:" + userId);
- return new ResponseResult(200, "退出成功");
- }
- }
5.5.3.2.SysUserServiceImpl
- @Service
- public class SysUserServiceImpl implements SysUserService {
-
- @Autowired
- private SysUserDao systemUserDao;
-
- /**
- * 根据用户名获取用户信息
- */
- @Override
- public SysUser getUserInfo(String username) {
- return systemUserDao.getUserInfo(username);
- }
- }
5.5.3.3.UserDetailsServiceImpl
- /**
- * 重写框架的 UserDetailsService 的 loadUserByUsername 方法
- * 从数据库中查询用户信息
- * 因为原框架的内用信息是存在内存中的
- */
- @Service
- public class UserDetailsServiceImpl implements UserDetailsService {
-
- @Autowired
- private SysUserService sysUserService;
-
- @Autowired
- private SysMenuDao sysMenuDao;
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- SysUser userInfo = sysUserService.getUserInfo(username);
- if (Objects.isNull(userInfo)) {
- throw new RuntimeException("用户名和密码错误!");
- }
- List<String> list = sysMenuDao.selectPermsByUserId(userInfo.getId());
- // 把数据封装成 UserDetails 返回,参数:用户信息、权限列表
- return new LoginUser(userInfo, list);
- }
- }
- @SpringBootApplication
- @MapperScan("com/example/boot_security/**/dao")
- public class BootSecurityApplication {
- public static void main(String[] args) {
- SpringApplication.run(BootSecurityApplication.class, args);
- }
- }
项目结构
- # Spring 高版本Springboot+swagger3.0配置,不然报错
- spring:
- mvc:
- pathmatch:
- matching-strategy: ant_path_matcher
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeHttpRequests()
- //对于登录login 注册register 验证码captchaImage 允许匿名访问
- .antMatchers("/login/**").permitAll()
- //静态资源,可匿名访问
- .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
- .antMatchers("/swagger-ui.html", "/doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
- .anyRequest()
- //所有请求都必须认证
- .authenticated()
- .and()
- .formLogin()
- .and()
- .exceptionHandling()
- //没登录提示信息
- .authenticationEntryPoint((req, resp, ex) -> {
- resp.setContentType("application/json;charset=UTF-8");
- resp.setStatus(HttpStatus.UNAUTHORIZED.value());
- resp.getWriter().println("必须认证之后才能访问!");
- })
- //退出登录提示信息
- .and().logout().logoutUrl("/logout")
- .logoutSuccessHandler((req, resp, ex) -> {
- resp.setContentType("application/json;charset=UTF-8");
- resp.setStatus(HttpStatus.UNAUTHORIZED.value());
- resp.getWriter().println("注销成功!");
- })
- //跨域处理方案
- .and().cors().configurationSource(configurationSource())
- //关闭csrf
- .and().csrf().disable()
- ;
- <!--swagger 高版本Springboot2.6+使用3.0以上-->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>3.0.0</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-boot-starter</artifactId>
- <version>3.0.0</version>
- </dependency>
- <!--swagger ui 高版本Springboot2.6+使用3.0以上-->
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-spring-boot-starter</artifactId>
- <version>3.0.3</version>
- </dependency>
- import io.swagger.annotations.ApiOperation;
- import org.springframework.beans.BeansException;
- import org.springframework.beans.factory.config.BeanPostProcessor;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.util.ReflectionUtils;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.ParameterBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.schema.ModelRef;
- import springfox.documentation.service.*;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spi.service.contexts.SecurityContext;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
- import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
-
- import java.lang.reflect.Field;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- import java.util.stream.Collectors;
-
- @Configuration
- public class SwaggerConfig {
-
- /**
- * swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等
- *
- * @return Docket
- */
- @Bean(value = "defaultApi2")
- public Docket defaultApi2() {
- return new Docket(DocumentationType.SWAGGER_2)
- //是否启用Swagger
- .enable(true)
- .apiInfo(apiInfo())
- .select()
- //此包路径下的类,才生成接口文档
- //.apis(RequestHandlerSelectors.basePackage("com.cn"))
- //扫描所有
- //.apis(RequestHandlerSelectors.any())
- //加了ApiOperation注解的类,才生成接口文档
- .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
- .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
- .paths(PathSelectors.any())
- .build()
- //设置自定义swagger请求头
- .securitySchemes(Collections.singletonList(securityScheme()))
- .securityContexts(securityContexts());
- //.globalOperationParameters(setHeaderToken());
- }
- /**
- * 设置自定义swagger请求头
- * @return ApiKey
- */
- @Bean
- SecurityScheme securityScheme() {
- return new ApiKey("Access-Token", "Access-Token", "header");
- }
-
- /**
- * 新增 securityContexts 保持登录状态
- */
- private List<SecurityContext> securityContexts() {
- return new ArrayList(
- Collections.singleton(SecurityContext.builder()
- .securityReferences(defaultAuth())
- .forPaths(PathSelectors.regex("^(?!auth).*$"))
- .build())
- );
- }
-
- /**
- * 配置
- */
- private List<SecurityReference> defaultAuth() {
- AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
- AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
- authorizationScopes[0] = authorizationScope;
- return new ArrayList(
- Collections.singleton(new SecurityReference("Access-Token", authorizationScopes)));
- }
-
- /**
- * swagger主页名
- * @return ApiInfo
- */
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("接口文档")
- .description("swagger接口文档")
- .version("1.0")
- .build();
- }
-
- /**
- * JWT token
- * @return setHeaderToken
- */
- private List<Parameter> setHeaderToken() {
- ParameterBuilder tokenPar = new ParameterBuilder();
- List<Parameter> pars = new ArrayList<>();
- tokenPar.name("Access-Token").description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
- pars.add(tokenPar.build());
- return pars;
- }
-
- /**
- * 高版本Springboot2.6+务必加入配置类中的springfoxHandlerProviderBeanPostProcessor方法
- * @return BeanPostProcessor
- */
- @Bean
- public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
- return new BeanPostProcessor() {
-
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
- customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
- }
- return bean;
- }
-
- private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
- List<T> copy = mappings.stream()
- .filter(mapping -> mapping.getPatternParser() == null)
- .collect(Collectors.toList());
- mappings.clear();
- mappings.addAll(copy);
- }
-
- @SuppressWarnings("unchecked")
- private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
- try {
- Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
- field.setAccessible(true);
- return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
- } catch (IllegalArgumentException | IllegalAccessException e) {
- throw new IllegalStateException(e);
- }
- }
- };
- }
-
- }
-
-
由于RBAC模型主要包含用户、角色、权限。因此本文主要设计了五张表:sys_user(用户表)、sys_menu(菜单表)、sys_role(权限表)、sys_role_menu(角色权限关联表)、sys_user_role(用户角色关联表)。
- CREATE TABLE `sys_menu` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
- `menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名',
- `path` varchar(200) DEFAULT NULL COMMENT '路由地址',
- `component` varchar(255) DEFAULT NULL COMMENT '组件路径',
- `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
- `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
- `perms` varchar(255) DEFAULT NULL COMMENT '权限标识',
- `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
- `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
- `modify_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
- `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
- `remark` varchar(500) DEFAULT NULL COMMENT '备注',
- PRIMARY KEY (`id`)
- )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- CREATE TABLE `sys_role` (
- `id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '主键',
- `name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '角色名称',
- `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串',
- `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
- `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
- `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
- `modify_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
- `update_by` bigint(200) DEFAULT NULL,
- `remark` varchar(500) DEFAULT NULL COMMENT '备注',
- PRIMARY KEY (`id`)
- )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- CREATE TABLE `sys_role_menu` (
- `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色id',
- `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '权限id',
- PRIMARY KEY (`role_id`,`menu_id`)
- )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- CREATE TABLE `sys_user` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
- `user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
- `nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',
- `password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',
- `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
- `email` varchar(64) DEFAULT 'NULL' COMMENT '邮箱',
- `mobile` varchar(32) DEFAULT 'NULL' COMMENT '手机号',
- `sex` char(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
- `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
- `modify_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
- `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
- `remark` varchar(500) DEFAULT NULL COMMENT '备注',
- PRIMARY KEY (`id`)
- )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- CREATE TABLE `sys_user_role` (
- `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色id',
- `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '权限id',
- PRIMARY KEY (`user_id`,`role_id`)
- )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
- INSERT INTO `zipkin`.`sys_user` (`id`, `user_name`, `password`, `status`, `create_time`, `modify_time`) VALUES ('1', 'admin', '$2a$10$GoLr2BQF77XaqSM9q3ETqu3fsbaIwOddz4YjvxoL8gGVph486OWmC', '1', '2022-05-26 10:42:58', '2022-05-26 10:42:58');
参考资料:
若依VUE分离版框架:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。