SpringBoot整合SpringSecurity+Redis权限控制


1、认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。


2、效果截图

2.1、登录接口

SpringBoot整合SpringSecurity+Redis权限控制

2.2、注册接口

SpringBoot整合SpringSecurity+Redis权限控制
SpringBoot整合SpringSecurity+Redis权限控制

2.3、管理员权限接口

SpringBoot整合SpringSecurity+Redis权限控制

2.4、普通用户权限接口

SpringBoot整合SpringSecurity+Redis权限控制

2.5、公共接口接口

SpringBoot整合SpringSecurity+Redis权限控制

2.6、Redis缓存效果

SpringBoot整合SpringSecurity+Redis权限控制

3、前期准备工作

3.1、导入相关依赖

 		<!-- 配置使用redis启动器 -->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-redis</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-security</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <dependency>             <groupId>org.mybatis.spring.boot</groupId>             <artifactId>mybatis-spring-boot-starter</artifactId>             <version>2.1.4</version>         </dependency>          <dependency>             <groupId>mysql</groupId>             <artifactId>mysql-connector-java</artifactId>             <!--            <scope>runtime</scope>-->         </dependency>          <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency> 

3.2、创建数据库

创建用户表sys_user:

CREATE TABLE `sys_user` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `name` varchar(255) NOT NULL,   `password` varchar(255) NOT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  

创建权限表sys_role:

CREATE TABLE `sys_role` (   `id` int(11) NOT NULL,   `name` varchar(255) NOT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  

创建用户-角色表sys_user_role:

CREATE TABLE `sys_user_role` (   `user_id` int(11) NOT NULL,   `role_id` int(11) NOT NULL,   PRIMARY KEY (`user_id`,`role_id`),   KEY `fk_role_id` (`role_id`),   CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,   CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  

初始化一下数据:

INSERT INTO `sys_role` VALUES (1, 'ROLE_admin'); INSERT INTO `sys_role` VALUES (2, 'ROLE_user');  INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$LiJV/NxnZK2fiUemUthYCeazPXV/RzW5KQWhz5CZdYCbsUHxcRZWK'); INSERT INTO `sys_user` VALUES (2, 'user', '$2a$10$LiJV/NxnZK2fiUemUthYCeazPXV/RzW5KQWhz5CZdYCbsUHxcRZWK');  INSERT INTO `sys_user_role` VALUES (1, 1); INSERT INTO `sys_user_role` VALUES (2, 2); 

数据库中的权限格式为ROLE_XXX,是Spring Security规定的(后面会讲其他方式)。


4、核心逻辑

SpringBoot整合SpringSecurity+Redis权限控制

5、项目结构

SpringBoot整合SpringSecurity+Redis权限控制

6、代码

6.1、Entity实体类

package com.yuange.demo.entity;  import java.io.Serializable;  /**  * @author lichangyuan  * @create 2021-03-16 17:09  */ public class SysRole implements Serializable {     static final long serialVersionUID = 1L;      private Integer id;      private String name;      // 省略getter/setter      public static long getSerialVersionUID() {         return serialVersionUID;     }      public Integer getId() {         return id;     }      public void setId(Integer id) {         this.id = id;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     } }   
package com.yuange.demo.entity;  import java.io.Serializable;  /**  * @author lichangyuan  * @create 2021-03-16 17:08  */ public class SysUser implements Serializable {     static final long serialVersionUID = 1L;      private Integer id;      private String name;      private String password;      // 省略getter/setter      public static long getSerialVersionUID() {         return serialVersionUID;     }      public Integer getId() {         return id;     }      public void setId(Integer id) {         this.id = id;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public String getPassword() {         return password;     }      public void setPassword(String password) {         this.password = password;     } }   
package com.yuange.demo.entity;  import java.io.Serializable;  /**  * @author lichangyuan  * @create 2021-03-16 17:09  */ public class SysUserRole implements Serializable {     static final long serialVersionUID = 1L;      private Integer userId;      private Integer roleId;      // 省略getter/setter      public static long getSerialVersionUID() {         return serialVersionUID;     }      public Integer getUserId() {         return userId;     }      public void setUserId(Integer userId) {         this.userId = userId;     }      public Integer getRoleId() {         return roleId;     }      public void setRoleId(Integer roleId) {         this.roleId = roleId;     } }   

6.2、Utils工具类

package com.yuange.demo.util;  import com.fasterxml.jackson.databind.ObjectMapper;  import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter;  /**  * @author lichangyuan  * @create 2021-03-18 14:43  */ public class CustomUtils {     /**      * 响应json数据给前端      *      * @param response      * @param obj      */     public static void sendJsonMessage(HttpServletResponse response, Object obj) {         try {             ObjectMapper objectMapper = new ObjectMapper();             response.setContentType("application/json; charset=utf-8");             PrintWriter writer = response.getWriter();             //Java对象转为Json格式的数据(objectMapper.writeValueAsString)             writer.print(objectMapper.writeValueAsString(obj));             writer.close();             //内容写到客户端浏览器             response.flushBuffer();         } catch (Exception e) {             e.printStackTrace();         }      } }  
package com.yuange.demo.util;   import java.io.Serializable;  public class JsonData implements Serializable {      /**      * 状态码 0表示成功过,-1,-2,-3、、、为失败      */     private Integer code;      /**      * 业务数据      */     private Object data;      /**      * 信息表示      */     private String msg;      public JsonData() {     }      public JsonData(Integer code, Object data, String msg) {         this.code = code;         this.data = data;         this.msg = msg;     }       /**      * 成功,不用返回数据      *      * @return      */     public static JsonData buildSuccess() {         return new JsonData(0, null, null);     }      /**      * 成功,返回数据      *      * @param data      * @return      */     public static JsonData buildSuccess(Object data) {         return new JsonData(0, data, null);     }       /**      * 失败,固定状态码      *      * @param msg      * @return      */     public static JsonData buildError(String msg) {         return new JsonData(-1, null, msg);     }       /**      * 失败,自定义错误码和信息      *      * @param code      * @param msg      * @return      */     public static JsonData buildError(Integer code, String msg) {         return new JsonData(code, null, msg);     }       public Integer getCode() {         return code;     }      public void setCode(Integer code) {         this.code = code;     }      public Object getData() {         return data;     }      public void setData(Object data) {         this.data = data;     }      public String getMsg() {         return msg;     }      public void setMsg(String msg) {         this.msg = msg;     } }  
package com.yuange.demo.util;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils;  import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit;  /** @author lichangyuan @create 2021-03-19 12:25 */  @Component public final class RedisUtil {      //注入自己写的redisTemplate     @Autowired     private RedisTemplate<String, Object> redisTemplate;      // =============================common============================     /**      * 指定缓存失效时间      * @param key  键      * @param time 时间(秒)      */     public boolean expire(String key, long time) {         try {             if (time > 0) {                 redisTemplate.expire(key, time, TimeUnit.SECONDS);             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }      /**      * 根据key 获取过期时间      * @param key 键 不能为null      * @return 时间(秒) 返回0代表为永久有效      */     public long getExpire(String key) {         return redisTemplate.getExpire(key, TimeUnit.SECONDS);     }       /**      * 判断key是否存在      * @param key 键      * @return true 存在 false不存在      */     public boolean hasKey(String key) {         try {             return redisTemplate.hasKey(key);         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 删除缓存      * @param key 可以传一个值 或多个      */     @SuppressWarnings("unchecked")     public void del(String... key) {         if (key != null && key.length > 0) {             if (key.length == 1) {                 redisTemplate.delete(key[0]);             } else {                 redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));             }         }     }       // ============================String=============================      /**      * 普通缓存获取      * @param key 键      * @return 值      */     public Object get(String key) {         return key == null ? null : redisTemplate.opsForValue().get(key);     }      /**      * 普通缓存放入      * @param key   键      * @param value 值      * @return true成功 false失败      */      public boolean set(String key, Object value) {         try {             redisTemplate.opsForValue().set(key, value);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 普通缓存放入并设置时间      * @param key   键      * @param value 值      * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期      * @return true成功 false 失败      */      public boolean set(String key, Object value, long time) {         try {             if (time > 0) {                 redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);             } else {                 set(key, value);             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 递增      * @param key   键      * @param delta 要增加几(大于0)      */     public long incr(String key, long delta) {         if (delta < 0) {             throw new RuntimeException("递增因子必须大于0");         }         return redisTemplate.opsForValue().increment(key, delta);     }       /**      * 递减      * @param key   键      * @param delta 要减少几(小于0)      */     public long decr(String key, long delta) {         if (delta < 0) {             throw new RuntimeException("递减因子必须大于0");         }         return redisTemplate.opsForValue().increment(key, -delta);     }       // ================================Map=================================      /**      * HashGet      * @param key  键 不能为null      * @param item 项 不能为null      */     public Object hget(String key, String item) {         return redisTemplate.opsForHash().get(key, item);     }      /**      * 获取hashKey对应的所有键值      * @param key 键      * @return 对应的多个键值      */     public Map<Object, Object> hmget(String key) {         return redisTemplate.opsForHash().entries(key);     }      /**      * HashSet      * @param key 键      * @param map 对应多个键值      */     public boolean hmset(String key, Map<String, Object> map) {         try {             redisTemplate.opsForHash().putAll(key, map);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * HashSet 并设置时间      * @param key  键      * @param map  对应多个键值      * @param time 时间(秒)      * @return true成功 false失败      */     public boolean hmset(String key, Map<String, Object> map, long time) {         try {             redisTemplate.opsForHash().putAll(key, map);             if (time > 0) {                 expire(key, time);             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 向一张hash表中放入数据,如果不存在将创建      *      * @param key   键      * @param item  项      * @param value 值      * @return true 成功 false失败      */     public boolean hset(String key, String item, Object value) {         try {             redisTemplate.opsForHash().put(key, item, value);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }      /**      * 向一张hash表中放入数据,如果不存在将创建      *      * @param key   键      * @param item  项      * @param value 值      * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间      * @return true 成功 false失败      */     public boolean hset(String key, String item, Object value, long time) {         try {             redisTemplate.opsForHash().put(key, item, value);             if (time > 0) {                 expire(key, time);             }             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 删除hash表中的值      *      * @param key  键 不能为null      * @param item 项 可以使多个 不能为null      */     public void hdel(String key, Object... item) {         redisTemplate.opsForHash().delete(key, item);     }       /**      * 判断hash表中是否有该项的值      *      * @param key  键 不能为null      * @param item 项 不能为null      * @return true 存在 false不存在      */     public boolean hHasKey(String key, String item) {         return redisTemplate.opsForHash().hasKey(key, item);     }       /**      * hash递增 如果不存在,就会创建一个 并把新增后的值返回      *      * @param key  键      * @param item 项      * @param by   要增加几(大于0)      */     public double hincr(String key, String item, double by) {         return redisTemplate.opsForHash().increment(key, item, by);     }       /**      * hash递减      *      * @param key  键      * @param item 项      * @param by   要减少记(小于0)      */     public double hdecr(String key, String item, double by) {         return redisTemplate.opsForHash().increment(key, item, -by);     }       // ============================set=============================      /**      * 根据key获取Set中的所有值      * @param key 键      */     public Set<Object> sGet(String key) {         try {             return redisTemplate.opsForSet().members(key);         } catch (Exception e) {             e.printStackTrace();             return null;         }     }       /**      * 根据value从一个set中查询,是否存在      *      * @param key   键      * @param value 值      * @return true 存在 false不存在      */     public boolean sHasKey(String key, Object value) {         try {             return redisTemplate.opsForSet().isMember(key, value);         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 将数据放入set缓存      *      * @param key    键      * @param values 值 可以是多个      * @return 成功个数      */     public long sSet(String key, Object... values) {         try {             return redisTemplate.opsForSet().add(key, values);         } catch (Exception e) {             e.printStackTrace();             return 0;         }     }       /**      * 将set数据放入缓存      *      * @param key    键      * @param time   时间(秒)      * @param values 值 可以是多个      * @return 成功个数      */     public long sSetAndTime(String key, long time, Object... values) {         try {             Long count = redisTemplate.opsForSet().add(key, values);             if (time > 0)                 expire(key, time);             return count;         } catch (Exception e) {             e.printStackTrace();             return 0;         }     }       /**      * 获取set缓存的长度      *      * @param key 键      */     public long sGetSetSize(String key) {         try {             return redisTemplate.opsForSet().size(key);         } catch (Exception e) {             e.printStackTrace();             return 0;         }     }       /**      * 移除值为value的      *      * @param key    键      * @param values 值 可以是多个      * @return 移除的个数      */      public long setRemove(String key, Object... values) {         try {             Long count = redisTemplate.opsForSet().remove(key, values);             return count;         } catch (Exception e) {             e.printStackTrace();             return 0;         }     }      // ===============================list=================================      /**      * 获取list缓存的内容      *      * @param key   键      * @param start 开始      * @param end   结束 0 到 -1代表所有值      */     public List<Object> lGet(String key, long start, long end) {         try {             return redisTemplate.opsForList().range(key, start, end);         } catch (Exception e) {             e.printStackTrace();             return null;         }     }       /**      * 获取list缓存的长度      *      * @param key 键      */     public long lGetListSize(String key) {         try {             return redisTemplate.opsForList().size(key);         } catch (Exception e) {             e.printStackTrace();             return 0;         }     }       /**      * 通过索引 获取list中的值      *      * @param key   键      * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推      */     public Object lGetIndex(String key, long index) {         try {             return redisTemplate.opsForList().index(key, index);         } catch (Exception e) {             e.printStackTrace();             return null;         }     }       /**      * 将list放入缓存      *      * @param key   键      * @param value 值      */     public boolean lSet(String key, Object value) {         try {             redisTemplate.opsForList().rightPush(key, value);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 将list放入缓存      * @param key   键      * @param value 值      * @param time  时间(秒)      */     public boolean lSet(String key, Object value, long time) {         try {             redisTemplate.opsForList().rightPush(key, value);             if (time > 0)                 expire(key, time);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }      }       /**      * 将list放入缓存      *      * @param key   键      * @param value 值      * @return      */     public boolean lSet(String key, List<Object> value) {         try {             redisTemplate.opsForList().rightPushAll(key, value);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }      }       /**      * 将list放入缓存      *      * @param key   键      * @param value 值      * @param time  时间(秒)      * @return      */     public boolean lSet(String key, List<Object> value, long time) {         try {             redisTemplate.opsForList().rightPushAll(key, value);             if (time > 0)                 expire(key, time);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 根据索引修改list中的某条数据      *      * @param key   键      * @param index 索引      * @param value 值      * @return      */      public boolean lUpdateIndex(String key, long index, Object value) {         try {             redisTemplate.opsForList().set(key, index, value);             return true;         } catch (Exception e) {             e.printStackTrace();             return false;         }     }       /**      * 移除N个值为value      *      * @param key   键      * @param count 移除多少个      * @param value 值      * @return 移除的个数      */      public long lRemove(String key, long count, Object value) {         try {             Long remove = redisTemplate.opsForList().remove(key, count, value);             return remove;         } catch (Exception e) {             e.printStackTrace();             return 0;         }      }  } 

6.3、Handler层

注销处理器

package com.yuange.demo.handler;  import com.yuange.demo.util.CustomUtils; import com.yuange.demo.util.JsonData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component;  import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  /**  * 注销处理器  *  * @author lichangyuan  * @create 2021-03-18 14:16  */ @Component public class AuthenticationLogout implements LogoutSuccessHandler {      @Autowired     StringRedisTemplate stringRedisTemplate;      @Override     public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {         String token = request.getHeader("token");         if (token == null) {             token = request.getParameter("token");         }         try {             if (token == null) {                 //token为空表示未登录,注销失败                 CustomUtils.sendJsonMessage(response, JsonData.buildError("未登录,不能进行注销操作!!!"));             } else {                 String username = stringRedisTemplate.opsForValue().get(token);                 if (username == null) {                     //token不正确,注销失败                     CustomUtils.sendJsonMessage(response, JsonData.buildError("登录凭证异常,注销失败!!!"));                 } else {                     //token正确,注销成功                     CustomUtils.sendJsonMessage(response, JsonData.buildError("注销成功"));                     //清空token                     stringRedisTemplate.delete(token);                 }             }         } catch (Exception e) {             e.printStackTrace();         }         CustomUtils.sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));     }  }  

未登录时处理器

package com.yuange.demo.handler;  /**  * 未登录时处理器  * @author lichangyuan  * @create 2021-03-18 14:41  */  import com.yuange.demo.util.CustomUtils; import com.yuange.demo.util.JsonData; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint;  import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  /**  * 未登录时处理器  */ public class TokenAuthenticationEntryPoint implements AuthenticationEntryPoint {       @Override     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {         CustomUtils.sendJsonMessage(response, JsonData.buildError("请登录!!"));     } }  

权限不足处理器

package com.yuange.demo.handler;  import com.yuange.demo.util.CustomUtils; import com.yuange.demo.util.JsonData; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler;   import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  /**  * 权限不足处理器  */ public class TokenAccessDeniedHandler implements AccessDeniedHandler {       @Override     public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {         CustomUtils.sendJsonMessage(response, JsonData.buildError("权限不够,请联系管理员!!!"));     }   }  

6.4、自定义Filter

请求过滤器 , token没有或者不正确的时候, 告诉用户执行相应操作,token正确且未认真的情况下则放行请求, 交由认证过滤器进行认证操作

package com.yuange.demo.filter;  /**  * @author lichangyuan  * @create 2021-03-18 14:45  */   import com.yuange.demo.service.impl.UserServiceImpl; import com.yuange.demo.util.CustomUtils; import com.yuange.demo.util.JsonData; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;  import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  /**  * 自定义请求过滤器,token没有或者不正确的时候,  * 告诉用户执行相应操作,token正确且未认真的情况下则放行请求,  * 交由认证过滤器进行认证操作  */ public class OncePerRequestAuthoricationFilter extends BasicAuthenticationFilter {      StringRedisTemplate stringRedisTemplate;      UserServiceImpl userServiceImpl;      public OncePerRequestAuthoricationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate, UserServiceImpl userServiceImpl) {         super(authenticationManager);         this.stringRedisTemplate=stringRedisTemplate;         this.userServiceImpl=userServiceImpl;     }      @Override     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {         String token=request.getHeader("token");         if(token==null || token.equals("")){             //token为空,则返回空             chain.doFilter(request, response);         }         String username=stringRedisTemplate.opsForValue().get(token);         try{             //判断token情况,给予对应的处理方案             if(username==null){                 CustomUtils.sendJsonMessage(response, JsonData.buildError("登录凭证不正确或者超时了,请重新登录!!!"));             }else{                 UserDetails userDetails = userServiceImpl.loadUserByUsername(username);                 if(userDetails!=null){                     UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(username,null,userDetails.getAuthorities());                     response.setHeader("token",token);                     SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);                 }             }         }catch (Exception e){             CustomUtils.sendJsonMessage(response, JsonData.buildError("登录凭证异常!!!"));         }         super.doFilterInternal(request,response,chain);     } }  

认证过滤器, 判断认证成功还是失败,并给予相对应的逻辑处理

package com.yuange.demo.filter;  /**  * @author lichangyuan  * @create 2021-03-18 14:56  */  import com.yuange.demo.entity.SysUser; import com.yuange.demo.mapper.SysUserMapper; import com.yuange.demo.util.CustomUtils; import com.yuange.demo.util.JsonData; import com.yuange.demo.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; 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 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.UUID; import java.util.concurrent.TimeUnit;  /**  * 自定义认证过滤器,判断认证成功还是失败,并给予相对应的逻辑处理  */ public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {       AuthenticationManager authenticationManager;      StringRedisTemplate stringRedisTemplate;       public AuthenticationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate) {         this.authenticationManager = authenticationManager;         this.stringRedisTemplate = stringRedisTemplate;     }        //未认证时调用此方法,判断认证是否成功,认证成功与否由authenticationManager.authenticate()去判断,我们在这里只负责传递所需要的参数即可     @Override     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {         String username = request.getParameter("username");         String password = request.getParameter("password");         return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>()));     }      //验证成功操作     @Override     protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {         /**          * 验证成功则向redis缓存写入token,然后在响应头添加token,并向前端返回          */         String token = UUID.randomUUID().toString().replaceAll("-", "");  //token本质就是随机生成的字符串         //由于不能使用@Autowired因此使用stringRedisTemplate         stringRedisTemplate.opsForValue().set(token, request.getParameter("username"), 60 * 10, TimeUnit.SECONDS);    //存入缓存中         response.setHeader("token", token);  //在响应头添加token         CustomUtils.sendJsonMessage(response, JsonData.buildSuccess(token));     }      //验证失败     @Override     protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {         /**          * 验证成功则向前端返回失败原因          */         CustomUtils.sendJsonMessage(response, JsonData.buildError("账号或者密码错误"));     } }  

6.5、自定义Config

springsecurity核心配置文件,无论是处理器还是过滤器都需要注入到此

package com.yuange.demo.config;  import com.yuange.demo.filter.AuthenticationFilter; import com.yuange.demo.filter.OncePerRequestAuthoricationFilter; import com.yuange.demo.handler.AuthenticationLogout; import com.yuange.demo.handler.TokenAccessDeniedHandler; import com.yuange.demo.handler.TokenAuthenticationEntryPoint; import com.yuange.demo.service.impl.UserServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.core.StringRedisTemplate; 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.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;   /**  * @author lichangyuan  * @create 2021-03-18 15:56  */ @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {      //java操作redis的string类型数据的类     @Autowired     StringRedisTemplate stringRedisTemplate;      //注销处理器     @Autowired     AuthenticationLogout authenticationLogout;      //加密     @Bean     public BCryptPasswordEncoder bCryptPasswordEncoder() {         return new BCryptPasswordEncoder();     }       @Bean     public UserDetailsService userDetailsService() {         return new UserServiceImpl();     }       /**      * 认证      *      * @param auth      * @throws Exception      */     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());     }       /**      * 授权      *      * @param http      * @throws Exception      */     @Override     protected void configure(HttpSecurity http) throws Exception {         //权限管理         http.authorizeRequests().antMatchers("/").permitAll()                 .antMatchers("/admin/**").hasRole("admin")                 .antMatchers("/user/**").hasRole("user")                  .and()                 //开启跨域访问                 .cors().and().                 //关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求                 csrf().disable()                 .authorizeRequests()                 //任何请求方式                 .anyRequest().permitAll()                  .and()                 .logout()                 .permitAll()                 .logoutSuccessHandler(authenticationLogout) //注销时的逻辑处理                  .and()                 .addFilter(new AuthenticationFilter(authenticationManager(), stringRedisTemplate))   //自定义认证过滤器                 .addFilter(new OncePerRequestAuthoricationFilter(authenticationManager(), stringRedisTemplate, (UserServiceImpl) userDetailsService())) //自定义请求过滤器                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)     //去除默认的session、cookie                  .and()                 .exceptionHandling().authenticationEntryPoint(new TokenAuthenticationEntryPoint())  //未登录时的逻辑处理                 .accessDeniedHandler(new TokenAccessDeniedHandler());    //权限不足时的逻辑处理     }       /**      * 用于解决跨域问题      *      * @return      */     @Bean     CorsConfigurationSource corsConfigurationSource() {         final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();         source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());         return source;     } }  

Redis相应的配置

package com.yuange.demo.config;  import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; 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.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;  /**  * @author lichangyuan  * @create 2021-03-19 12:24  */ @Configuration public class RedisConfig {      @Bean     @SuppressWarnings("all")     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {         //先改成<String, Object>类型         RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();         template.setConnectionFactory(factory);          //Json序列化配置         //1、json解析任意的对象(Object),变成json序列化         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);         //用ObjectMapper进行转义         ObjectMapper om = new ObjectMapper();         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);         //该方法是指定序列化输入的类型,就是将数据库里的数据按照一定类型存储到redis缓存中。         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);         jackson2JsonRedisSerializer.setObjectMapper(om);         //2、String的序列化         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();          // key采用String的序列化方式         template.setKeySerializer(stringRedisSerializer);         // hash的key也采用String的序列化方式         template.setHashKeySerializer(stringRedisSerializer);         // value序列化方式采用jackson         template.setValueSerializer(jackson2JsonRedisSerializer);         // hash的value序列化方式采用jackson         template.setHashValueSerializer(jackson2JsonRedisSerializer);         template.afterPropertiesSet();          return template;     } }  

6.6、Service逻辑层

UserServiceImpl.java, 实现UserDetailsService里面的loadUserByUsername()方法,AuthenticationManager会调用此方法去获取用户数据信息,从而完成认证。

package com.yuange.demo.service.impl;  import com.yuange.demo.entity.SysRole; import com.yuange.demo.entity.SysUser; import com.yuange.demo.entity.SysUserRole; import com.yuange.demo.service.SysRoleService; import com.yuange.demo.service.SysUserRoleService; import com.yuange.demo.service.SysUserService; import com.yuange.demo.service.UserService; import com.yuange.demo.util.JsonData; import com.yuange.demo.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service;  import java.util.ArrayList; import java.util.List;  /**  * @author lichangyuan  * @create 2021-03-18 14:49  */ @Service public class UserServiceImpl implements UserService, UserDetailsService {     @Autowired     SysUserService sysUserService;      @Autowired     SysRoleService sysRoleService;      @Autowired     SysUserRoleService sysUserRoleService;      @Autowired     RedisUtil redisUtil;       /**      * 实现UserDetailsService接口的方法,用于获取用户个人信息      *      * @param username      * @return      * @throws UsernameNotFoundException      */     @Override     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {          //根据用户名查找用户,         SysUser user = sysUserService.selectByName(username);         if (user == null) {             throw new UsernameNotFoundException("用户名错误!!");         }          //获取用户权限,并把其添加到GrantedAuthority中         List<GrantedAuthority> grantedAuthorities = new ArrayList<>();          // 添加权限         List<SysUserRole> userRoles = sysUserRoleService.listByUserId(user.getId());         for (SysUserRole userRole : userRoles) {             SysRole role = sysRoleService.selectById(userRole.getRoleId());             grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));         }         //用户名,密码,权限         return new User(username, user.getPassword(), grantedAuthorities);     }      /**      * 注册操作      *      * @param user      * @return      */     public JsonData register(SysUser user) {         user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));  //对密码进行加密         int insert = sysUserService.insert(user);         if (insert > 0) {             return JsonData.buildSuccess("注册成功!");         } else {             return JsonData.buildError("注册失败!");         }     } }  
package com.yuange.demo.service;  /**  * @author lichangyuan  * @create 2021-03-18 14:50  */ public interface UserService { }  
package com.yuange.demo.service;  import com.yuange.demo.entity.SysUser; import com.yuange.demo.mapper.SysUserMapper; import com.yuange.demo.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;  /**  * @author lichangyuan  * @create 2021-03-16 17:10  */ @Service public class SysUserService {     @Autowired     private SysUserMapper userMapper;      @Autowired     RedisUtil redisUtil;      public SysUser selectById(Integer id) {         SysUser user = (SysUser) redisUtil.hget("SysUserService","selectById"+id);         if(user==null){             user=userMapper.selectById(id);             redisUtil.hset("SysUserService","selectById" + id, user, 180);         }         return user;     }      public SysUser selectByName(String name) {         SysUser user = (SysUser) redisUtil.hget("SysUserService","selectByName"+name);         if(user==null){             user=userMapper.selectByName(name);             redisUtil.hset("SysUserService","selectByName" + name, user, 180);         }         return user;     }      public Integer insert(SysUser sysUser){         redisUtil.del("SysUserService");         return userMapper.insert(sysUser);     } }   
package com.yuange.demo.service;  import com.yuange.demo.entity.SysRole; import com.yuange.demo.entity.SysUserRole; import com.yuange.demo.mapper.SysUserRoleMapper; import com.yuange.demo.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;  import java.util.List;  /**  * @author lichangyuan  * @create 2021-03-16 17:11  */ @Service public class SysUserRoleService {     @Autowired     private SysUserRoleMapper userRoleMapper;      @Autowired     RedisUtil redisUtil;      public List<SysUserRole> listByUserId(Integer userId) {         List<SysUserRole> sysUserRoleList = (List<SysUserRole>) redisUtil.hget("SysUserRoleService","listByUserId" + userId);         if (sysUserRoleList == null) {             sysUserRoleList = userRoleMapper.listByUserId(userId);             redisUtil.hset("SysUserRoleService","listByUserId" + userId, sysUserRoleList, 180);         }         return sysUserRoleList;     } }   
package com.yuange.demo.service;  import com.yuange.demo.entity.SysRole; import com.yuange.demo.mapper.SysRoleMapper; import com.yuange.demo.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;  /**  * @author lichangyuan  * @create 2021-03-16 17:11  */ @Service public class SysRoleService {     @Autowired     private SysRoleMapper roleMapper;      @Autowired     RedisUtil redisUtil;      public SysRole selectById(Integer id) {         SysRole sysRole = (SysRole) redisUtil.hget("SysRoleService","selectById" + id);         if (sysRole == null) {             sysRole = roleMapper.selectById(id);             redisUtil.hset("SysRoleService","selectById" + id, sysRole, 180);         }         return sysRole;     } }   

6.7、Mapper持久层

package com.yuange.demo.mapper;  import com.yuange.demo.entity.SysRole; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select;  /**  * @author lichangyuan  * @create 2021-03-16 17:10  */ @Mapper public interface SysRoleMapper {     @Select("SELECT * FROM sys_role WHERE id = #{id}")     SysRole selectById(Integer id); }   
package com.yuange.demo.mapper;  import com.yuange.demo.entity.SysUser; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select;  /**  * @author lichangyuan  * @create 2021-03-16 17:09  */ @Mapper public interface SysUserMapper {     @Select("SELECT * FROM sys_user WHERE id = #{id}")     SysUser selectById(Integer id);      @Select("SELECT * FROM sys_user WHERE name = #{name}")     SysUser selectByName(String name);      @Insert("insert into sys_user values ( null, #{name}, #{password})")     Integer insert(SysUser sysUser); }   
package com.yuange.demo.mapper;  import com.yuange.demo.entity.SysUserRole; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select;  import java.util.List;  /**  * @author lichangyuan  * @create 2021-03-16 17:10  */ @Mapper public interface SysUserRoleMapper {     @Select("SELECT * FROM sys_user_role WHERE user_id = #{userId}")     List<SysUserRole> listByUserId(Integer userId); }   

6.8、Controller控制层

UserController.java , @PreAuthorize(“hasRole(‘ROLE_USER’)”) 指定接口拥有ROLE_USER的权限方可访问的注解。但是我没有使用下面我给注释掉了,我使用了自定义配置路径在SecurityConfig类中(自定义WebSecurityConfigurerAdapter)

package com.yuange.demo.controller;  import com.yuange.demo.entity.SysUser; import com.yuange.demo.service.impl.UserServiceImpl; import com.yuange.demo.util.JsonData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import sun.misc.BASE64Decoder;  import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map;  /**  * @author lichangyuan  * @create 2021-03-18 17:31  */ @RestController public class UserController {      @Autowired     UserServiceImpl userServiceImpl;  //    @Autowired //    AuthenticationManager authenticationManager;      /**      * 注册操作      *      * @param user      * @return      */     @PostMapping("/register")     public JsonData register(@RequestBody SysUser user) {         return JsonData.buildSuccess(userServiceImpl.register(user));     }      /**      * 当权限为ROLE_ADMIN时方可访问,否则抛出权限不足异常      *      * @return      */     @GetMapping("/admin") //    @PreAuthorize("hasRole('ROLE_admin')")     public JsonData index() {         String username;         Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();          if (principal instanceof UserDetails) {             username = ((UserDetails) principal).getUsername();         } else {             username = principal.toString();         }         return JsonData.buildSuccess(username);     }      /**      * 当权限为ROLE_USER时方可访问,否则抛出权限不足异常      *      * @return      */     @GetMapping("/user") //    @PreAuthorize("hasRole('ROLE_user')")     public JsonData hello() {         String username;         //获取当前用户信息         Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();          if (principal instanceof UserDetails) {             username = ((UserDetails) principal).getUsername();         } else {             username = principal.toString();         }          return JsonData.buildSuccess(username);     }  //    @PostMapping(value = "login") //    public JsonData login(@RequestBody Map<String,String> params)  { //        UserInfo userInfo = SecurityUtils.login(params.get("username"), params.get("password"), authenticationManager); //        return JsonData.buildSuccess(userInfo); //    }      @RequestMapping("pub")     public JsonData pub(){         String username;         //获取当前用户信息         Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();          if (principal instanceof UserDetails) {             username = ((UserDetails) principal).getUsername();         } else {             username = principal.toString();         }          return JsonData.buildSuccess(username);     }  }  

6.9、application.properties配置

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123123123  mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl  #开启Mybatis下划线命名转驼峰命名 mybatis.configuration.map-underscore-to-camel-case=true  server.port=8080 spring.web.resources.static-locations=classpath:/METAINF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/   # REDIS (RedisProperties) # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.jedis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.jedis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.jedis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.jedis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=5000    

7、其他

7.1、Spring Security的权限配置不生效问题

在集成Spring Security做接口权限配置时,在给用户配置的权限后,还是一直显示“无权限”或者"权限不足"。
1、不生效的例子:
接口

@RequestMapping("/admin")     @ResponseBody     @PreAuthorize("hasRole('ADMIN')")     public String printAdmin() {         return "如果你看见这句话,说明你有ROLE_ADMIN角色";     }      @RequestMapping("/user")     @ResponseBody     @PreAuthorize("hasRole('USER')")     public String printUser() {         return "如果你看见这句话,说明你有ROLE_USER角色";     }  

SecurityConfig

	.and()       .authorizeRequests()       .antMatchers("/user").hasAnyRole("USER")        .antMatchers("/admin").hasAnyRole("ADMIN")       .anyRequest().authenticated() //必须授权才能范围  

SpringBoot整合SpringSecurity+Redis权限控制
2、解决办法

经测试,只有用户携带权限的字段为 “ROLE_” + 接口/配置 中的权限字段,才能控制生效,举例:
将上面的用户携带权限改为
SpringBoot整合SpringSecurity+Redis权限控制

7.2、Bug笔记

没有被spring管理的对象,即自己new的一个对象,其无论如何都不会通过@Autowired注入成功

Spring Boot中由于序列化方式不同因此不能混合使用StringRedisTemplate和RedisTemplate

版权声明:玥玥 发表于 2021-03-23 4:55:59。
转载请注明:SpringBoot整合SpringSecurity+Redis权限控制 | 女黑客导航