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/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() //必须授权才能范围
2、解决办法
经测试,只有用户携带权限的字段为 “ROLE_” + 接口/配置 中的权限字段,才能控制生效,举例:
将上面的用户携带权限改为
7.2、Bug笔记
没有被spring管理的对象,即自己new的一个对象,其无论如何都不会通过@Autowired注入成功
Spring Boot中由于序列化方式不同因此不能混合使用StringRedisTemplate和RedisTemplate