# rainbow-security-boot-starter **Repository Path**: rainbow-parent/rainbow-security-boot-starter ## Basic Information - **Project Name**: rainbow-security-boot-starter - **Description**: 一款基于redis的权限框架,springboot start可以轻松和springboot集成,简单好用。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 0 - **Created**: 2020-12-24 - **Last Updated**: 2021-06-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

rainbow-security-boot-starter

一款基于springboot+redis的权限框架

## 1.简介 ​ 对比`java`市场上的两大安全框架`shiro`和`spring security`,rainbow-security-boot-starter的设计初衷就是为了让权限框架使用的更为简单,快速应用。 ## 2. 如何使用 > 实现声明:rainbow-security-boot-starter不会去控制你登录的业务逻辑,你只需要判断用户信息一切准确无误的时候,调用RainbowSecurityUtils下的方法即可。 ### 2.1 配置拦截器 ```java package org.rainbow.config; import com.rainbow.security.interceptor.RainbowSecurityInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author lihao3 * @Date 2020/9/30 14:17 */ @Configuration public class RainbowSecurityConfig implements WebMvcConfigurer { /** * 注册rainbow-security-boot-starter的拦截器,打开注解式鉴权功能 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 全局拦截器 registry.addInterceptor(new RainbowSecurityInterceptor()) .addPathPatterns("/**") .excludePathPatterns("添加不需要登陆即可访问的接口URL"); } } ``` ### 2.2 配置yaml文件 ```yaml rainbow: security: is-read-body: false # 是否尝试从请求体里读取token is-read-cookie: false # 是否尝试从cookie里读取token timeout: 172800 # token有效期,单位s 默认1天 token-name: rainbowSecurity # token名称 (同时也是cookie名称) is-read-head: false # 是否尝试从header里读取token is-share: false # 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录) ``` ### 2.3 执行登陆方法 > 如果 is-share为true的时候,如果A已经登陆了账号a并拿到了tokenA,这时候如果B也登陆了账号a,那么他也将返回tokenA > > 如果 is-share为false的时候,如果A已经登陆了账号a并拿到了tokenA,这时候如果B也登陆了账号a,那么tokenA作废,B则返回 ```java // loginId是用户唯一标识,token为登陆之后返回的令牌 String token = RainbowSecurityUtils.login(loginId); ``` ### 2.4 将用户的一些个人信息放入到缓存中 ```java /** * 根据loginID将对应的数据缓存起来 * * @param loginID 用户唯一标识 * @param key 数据对应的key,如果为空则使用loginID作为key * @param load 存储的数据 */ RainbowSecurityUtils.setDataByLoginID(String loginID, String key, Object load); ``` ### 2.5 根据用户唯一标识获取缓存中对应的信息 ```java /** * 根据loginID获取他对于的缓存 * * @param loginID 用户唯一标识 * @param key 数据对应的key,如果为空则使用loginID作为key */ Object sysUser = RainbowSecurityUtils.getDataByLoginID(String loginID, String key); ``` ### 2.6 根据接口中的token获取对应loginID ```java String loginID = RainbowSecurityUtils.getLoginID(); ``` ### 2.7 获取接口中的token > 根据yml配置的获取token的方法来获取 > > 优先级:header > cookie > body ```java String token = RainbowSecurityUtils.getRequestToken(); ``` ### 2.8 通过loginID获取对应的token ```java String token = RainbowSecurityUtils.byLoginIDGetToken(String loginID); ``` ### 2.9 通过token获取对应的loginID > 如果token过期或者token异常则返回null ```java String token = RainbowSecurityUtils.byTokenGetLoginID(String token); ``` ### 2.10 退出登录 > 根据getRequestToken()方法获取接口中的token,让token作废 ```java RainbowSecurityUtils.logout(); ``` ### 2.11 判断当回话是否登录 > true代表已登录,反之 ```java Boolean isLogin = RainbowSecurityUtils.isLogin(); ``` ### 2.12 强T下线 > 根据loginID将用户强T下线 ```java RainbowSecurityUtils.forceLogout(String loginID) ``` ## 3. 控制接口的权限标识和角色 ### 3.1 实现根据loginID获取对应权限标识和角色的接口 ```java import com.rainbow.security.service.RainbowSecurityService; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * @author lihao3 * @Date 2020/12/24 16:49 */ @Service public class RainbowSecurityServiceImpl implements RainbowSecurityService { @Autowired private SysMenuMapper sysMenuMapper; @Override public List getPermissionCodeList(String s) { List permissionCodeList = new ArrayList<>(); permissionCodeList.add("admin:list"); permissionCodeList.add("role:list"); permissionCodeList.add("menu:list"); permissionCodeList.add("dept:list"); permissionCodeList.add("log:list"); return permissionCodeList; } @Override public List getRoleCodeList(String s) { List roleCodeList = new ArrayList<>(); roleCodeList.add("admin"); return roleCodeList; } } ``` ### 3.2 在需要控制的方法上添加注解 1. 需要拥有权限标识`system:list`才能访问的接口 ```java @RainbowCheckPerms("system:list") @GetMapping("/test") public Result test() { return Result.success(); } ``` 2. 需要拥有角色`admin`才能访问的接口 ```java @RainbowCheckRoles("admin") @GetMapping("/test") public Result test() { return Result.success(); } ``` ## 4. 常见BUG ### 4.1 redis存入的数据被序列化了,无法查看 > 这是使用了阿里的fastjson进行反序列化,市场上还有很多种实现方法,这里就不一一列举了 1. pom 坐标 ```xml com.alibaba fastjson ${fastjson.version} ``` 2. 实现RedisSerializer接口 ```java import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.util.Assert; import java.nio.charset.Charset; /** * Redis使用FastJson序列化 * * @author lihao3 */ public class FastJson2JsonRedisSerializer implements RedisSerializer { @SuppressWarnings("unused") private ObjectMapper objectMapper = new ObjectMapper(); public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJson2JsonRedisSerializer(Class 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); } public void setObjectMapper(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "'objectMapper' must not be null"); this.objectMapper = objectMapper; } protected JavaType getJavaType(Class clazz) { return TypeFactory.defaultInstance().constructType(clazz); } } ``` 3. 设置RedisTemplate的序列化方式 ```java import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.rainbow.security.config.FastJson2JsonRedisSerializer; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * redis配置 * * @author lihao3 */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean @SuppressWarnings(value = {"unchecked", "rawtypes"}) public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); com.rainbow.security.config.FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.afterPropertiesSet(); return template; } } ``` ## 5. 框架内的异常 1. `AdminMandatoryLogout`是指被此loginID已被强T下线 2. `MandatoryLogout`是指被挤下线的异常(此异常只有当is-share: false才会触发) 3. `NoPermsException`访问该接口的loginID没有访问该接口的权限时触发的异常 4. `NoTokenException`没有携带token访问需要登录才能访问的接口时触发的异常 5. `TokenTimeOutException`此token已经超时的异常