SpringBoot整合SpringSecurity+Redis权限控制

  • 1、认识SpringSecurity
  • 2、效果截图
    • 2.1、登录接口
    • 2.2、注册接口
    • 2.3、管理员权限接口
    • 2.4、普通用户权限接口
    • 2.5、公共接口接口
    • 2.6、Redis缓存效果
  • 3、前期准备工作
    • 3.1、导入相关依赖
    • 3.2、创建数据库
  • 4、核心逻辑
  • 5、项目结构
  • 6、代码
    • 6.1、Entity实体类
    • 6.2、Utils工具类
    • 6.3、Handler层
    • 6.4、自定义Filter
    • 6.5、自定义Config
    • 6.6、Service逻辑层
    • 6.7、Mapper持久层
    • 6.8、Controller控制层
    • 6.9、application.properties配置
  • 7、其他
    • 7.1、Spring Security的权限配置不生效问题
    • 7.2、Bug笔记

1、认识SpringSecurity

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

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

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


2、效果截图

2.1、登录接口

2.2、注册接口


2.3、管理员权限接口

2.4、普通用户权限接口

2.5、公共接口接口

2.6、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、核心逻辑

5、项目结构


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/setterpublic 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/setterpublic 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/setterpublic 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@Autowiredprivate 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 {@AutowiredStringRedisTemplate stringRedisTemplate;@Overridepublic 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("注销成功"));//清空tokenstringRedisTemplate.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 {@Overridepublic 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 {@Overridepublic 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;}@Overrideprotected 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()去判断,我们在这里只负责传递所需要的参数即可@Overridepublic 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<>()));}//验证成功操作@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {/*** 验证成功则向redis缓存写入token,然后在响应头添加token,并向前端返回*/String token = UUID.randomUUID().toString().replaceAll("-", "");  //token本质就是随机生成的字符串//由于不能使用@Autowired因此使用stringRedisTemplatestringRedisTemplate.opsForValue().set(token, request.getParameter("username"), 60 * 10, TimeUnit.SECONDS);    //存入缓存中response.setHeader("token", token);  //在响应头添加tokenCustomUtils.sendJsonMessage(response, JsonData.buildSuccess(token));}//验证失败@Overrideprotected 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类型数据的类@AutowiredStringRedisTemplate stringRedisTemplate;//注销处理器@AutowiredAuthenticationLogout authenticationLogout;//加密@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic UserDetailsService userDetailsService() {return new UserServiceImpl();}/*** 认证** @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());}/*** 授权** @param http* @throws Exception*/@Overrideprotected 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*/@BeanCorsConfigurationSource 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序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.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 {@AutowiredSysUserService sysUserService;@AutowiredSysRoleService sysRoleService;@AutowiredSysUserRoleService sysUserRoleService;@AutowiredRedisUtil redisUtil;/*** 实现UserDetailsService接口的方法,用于获取用户个人信息** @param username* @return* @throws UsernameNotFoundException*/@Overridepublic 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 {@Autowiredprivate SysUserMapper userMapper;@AutowiredRedisUtil 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 {@Autowiredprivate SysUserRoleMapper userRoleMapper;@AutowiredRedisUtil 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 {@Autowiredprivate SysRoleMapper roleMapper;@AutowiredRedisUtil 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 {@AutowiredUserServiceImpl 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=123123123mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl#开启Mybatis下划线命名转驼峰命名
mybatis.configuration.map-underscore-to-camel-case=trueserver.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() //必须授权才能范围


2、解决办法

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

7.2、Bug笔记

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

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

SpringBoot整合SpringSecurity+Redis权限控制相关推荐

  1. SpringBoot整合SpringSecurity实现权限控制(五):用户管理

    系列文章目录 <SpringBoot整合SpringSecurity实现权限控制(一):实现原理> <SpringBoot整合SpringSecurity实现权限控制(二):权限数据 ...

  2. SpringBoot整合Shiro实现权限控制,验证码

    本文介绍 SpringBoot 整合 shiro,相对于 Spring Security 而言,shiro 更加简单,没有那么复杂. 目前我的需求是一个博客系统,有用户和管理员两种角色.一个用户可能有 ...

  3. springboot整合security实现权限控制

    1.建表,五张表,如下: 1.1.用户表 CREATE TABLE `t_sys_user` (`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ...

  4. springboot整合shiro实现权限控制

    博主简介:原互联网大厂tencent员工,网安巨头Venustech员工,阿里云开发社区专家博主,微信公众号java基础笔记优质创作者,csdn优质创作博主,创业者,知识共享者,欢迎关注,点赞,收藏. ...

  5. springboot整合springsecurity安全框架(后端spring_security模块代码可直接使用,根据需求自定义修改)

    SpringSecurity简介 最下面有与springboot整合的模块代码 用户认证和用户授权 主要包含两部分:用户认证和用户授权 用户认证:进入用户登录时候,输入用户名密码,查询数据库查看是否正 ...

  6. Springboot整合SpringSecurity 04-启用登出logout功能

    Springboot整合SpringSecurity 04-启用登出logout功能 前面Springboot整合SpringSecurity 02-使用自定义登陆页面我们讲过了SpringSecur ...

  7. Springboot 整合SpringSecurity实现账号密码+手机验证码登陆

    Springboot 整合SpringSecurity实现账号密码+手机验证码登陆 示例说明 版本 示例安装 Spring-security 介绍 为什么不用 shiro Spring-Securit ...

  8. SpringBoot整合Shiro实现权限管理与登陆注册

    前言 Shiro解决了什么问题? 互联网无非就是一些用户C想要使用一些服务S的资源去完成某件事,S的资源不能说给谁用就给谁用,因此产生了权限的概念,即C必须有权限才能操作S的资源.S如何确定C就是C呢 ...

  9. SpringBoot学习-part69安全-权限控制注销

    找到合适的thymeleaf-extras-springsecurity版本 ctrl+ 左键 进入spring-boot-starter-parent 根据这些,应该导入thymeleaf-extr ...

最新文章

  1. CVPR 2021 Oral | Transformer再发力!华南理工和微信提出UP-DETR
  2. 关于在web项目中实现本地打印
  3. pandas 查询数据
  4. ABAP里的OAuth2.0 Standard Package
  5. Qt中的ui文件是c语言文件吗,c-Qt-UI文件未在Visual Studio中更新
  6. 第二章指南(4.2)添加 Controller
  7. html ctf查找,Web CTF 解题思路总结—南京邮电大学攻防平台writeup
  8. 新的一年,推荐一些好书给大家
  9. 颠覆三观,内存真能当SSD用了!!!
  10. 常用PMP资料下载地址
  11. vbm 分析_VBM
  12. @repository注解
  13. 计算机网络共享服务器,办公室如何搭建共享服务器或文件共享服务器
  14. hp450 g8摄像头打开后黑屏
  15. Quantifying causality in data science with quasi-experiments
  16. kafka安装部署和使用
  17. Linkedin领英如何添加或更改账号的邮箱地址
  18. 出国(澳大利亚)要求材料
  19. Oracle授权及创建同义词
  20. 用python进行点云体素下采样

热门文章

  1. 立体匹配算法-初步了解
  2. 40(公式累加求和)
  3. 概念理解:误差函数,补误差函数
  4. iperf测带宽使用方法
  5. JAVA泛型-泛型类的继承
  6. Azkaban启动服务完成后浏览器访问8443端口出错
  7. 集丰照明|中国 LED 产业情况和市场发展如何?
  8. Android的 API 框架,Android 声明式 UI 框架 Litho 初探 —— Sections API
  9. JAVA的八种基本类型
  10. Incorporating Copying Mechanism in Sequence-to-Sequence Learning