Springboot Spring Security +Jwt 动态完成 前后端分离认证授权
前言
权限模型采用RBAC角色模型 框架: SpringBoot ,Spring Security ,Mybatis Plus,Swgger2,Jwt 数据库: Redis,Mysql
一.httpBasic认证 (尝鲜)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.启动
简单的认证防君子不防小人
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
启动访问任何请求,需要认证
默认账号为user 密码打印在控制台
在浏览器输入路径,即可得到一次简单的认证
2.自定义httpBasic认证的账号密码
在配置文件配置即可
spring.security.user.name=admin
spring.security.user.password=admin
二.Springboot + Spring Security 前后端分离认证授权
1.准备工作
1)maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
2).统一异常码
/**
* @author 志
* @date 2022-08-25 9:10
*/
public enum ErrorCode {
/* 成功 */
SUCCESS(0, "成功"),
//失败
FAIL(-1, "失败"),
USER_ACCOUNT_EXPIRED(2002, "账号已过期"),
USER_CREDENTIALS_ERROR(2003, "密码错误"),
USER_CREDENTIALS_EXPIRED(2004, "密码过期"),
USER_ACCOUNT_DISABLE(2005, "账号不可用"),
USER_ACCOUNT_LOCKED(2006, "账号被锁定"),
USER_ACCOUNT_NOT_EXIST(2007, "账号不存在"),
LOGIN_TIME_EXPIRED(2010, "未登录或登陆已失效,请先登录"),
/** 没有权限 */
NO_PERMISSION(3001, "没有权限");
private Integer code;
private String message;
ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
/**
* 根据code获取message
*/
public static String getMessageByCode(Integer code) {
for (ErrorCode ele : values()) {
if (ele.getCode().equals(code)) {
return ele.getMessage();
}
}
return null;
}
}
3).统一返回结果
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "统一返回结果")
public class WrapperResult<T> implements Serializable {
private static final long serialVersionUID = 5778573516446596671L;
@ApiModelProperty(value = "返回码")
private Integer code = 0;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private T data;
public WrapperResult() {
}
public WrapperResult(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> WrapperResult<T> success(T data) {
return new WrapperResult(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMessage(), data);
}
public static <T> WrapperResult<T> success(T data, String message) {
message = message != null && message.length() > 0 ? message : ErrorCode.SUCCESS.getMessage();
return new WrapperResult(ErrorCode.SUCCESS.getCode(), message, data);
}
public static <T> WrapperResult<T> fail(T data) {
return new WrapperResult(ErrorCode.FAIL.getCode(), ErrorCode.FAIL.getMessage(), data);
}
public static <T> WrapperResult<T> fail(T data, String message) {
message = message != null && message.length() > 0 ? message : ErrorCode.FAIL.getMessage();
return new WrapperResult(ErrorCode.FAIL.getCode(), message, data);
}
}
4).配置文件
server:
port: 8888
spring:
main:
allow-bean-definition-overriding: true
datasource:
username: user
password: 123
url: jdbc:mysql://127.0.0.1:3306/myapp?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: 172.16.114.242
port: 6379
password: admin
mybatis-plus:
mapper-locations: classpath*:sql/**/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
call-setters-on-nulls: true
map-underscore-to-camel-case: true
5).jwt工具类
public class JwtUtil {
public static final Long JWT_TTL = 60 * 30 *1000L;
public static final String JWT_KEY = "xiaozhi";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("xiaozhi")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);
return builder.compact();
}
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
6).reids配置及工具类
redis序列化
/**
* Redis使用FastJson序列化
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}
redis工具类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
public void delCacheMapValue(final String key, final String hkey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
redis配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
7).全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = Exception.class)
public WrapperResult<Object> exceptionHandler(HandlerMethod handlerMethod, Exception e){
WrapperResult res =new WrapperResult();
this.logger.error("调用目标方法:"+handlerMethod+"出现异常 ",e);
res.setCode(ErrorCode.FAIL.getCode());
res.setMessage("调用目标服务异常,请联系管理员查看后台日志信息");
return res;
}
@ExceptionHandler(value = RuntimeException.class)
public WrapperResult<Object> exceptionHandler(HandlerMethod handlerMethod, RuntimeException e){
WrapperResult res =new WrapperResult();
this.logger.error("调用目标方法:"+handlerMethod+"出现异常 ",e);
res.setCode(ErrorCode.FAIL.getCode());
res.setMessage(e.getMessage());
return res;
}
}
8).sql语句
数据库表模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CPSZHrct-1663312446135)(C:/Users/h/AppData/Roaming/Typora/typora-user-images/image-20220905170209976.png)]
用户表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL COMMENT '用户编号',
`user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
`pwd` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户密码',
`email` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`sex` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
`user_statu` int(5) NULL DEFAULT NULL COMMENT '是否可用 0--可用 1--不可用',
`last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '上传登录时间',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
INSERT INTO `sys_user` VALUES (1, 'user', '$2a$10$sPbHeIKrKIobRnvv4wpzNuwszyAFGth8OliQCWKvJNVo31bF0YRCe', NULL, NULL, '2022-05-11 13:38:41', 1, NULL);
INSERT INTO `sys_user` VALUES (2, 'user2', '$2a$10$sPbHeIKrKIobRnvv4wpzNuwszyAFGth8OliQCWKvJNVo31bF0YRCe', NULL, NULL, '2022-08-29 11:51:21', 1, NULL);
SET FOREIGN_KEY_CHECKS = 1;
角色表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL COMMENT '编号',
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
`code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色编码',
`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
`role_statu` int(5) NULL DEFAULT NULL COMMENT '角色状态 0--可用 1--不可用',
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
INSERT INTO `sys_role` VALUES (1, '管理员', 'admin', NULL, '2022-08-23 16:25:14', 1);
INSERT INTO `sys_role` VALUES (2, '普通用户', 'user', NULL, '2022-08-23 16:25:49', 1);
SET FOREIGN_KEY_CHECKS = 1;
菜单表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL COMMENT '菜单id',
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父级id, 根菜单为0',
`menu_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名',
`srt` int(4) NULL DEFAULT 0 COMMENT '排序',
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '路由地址',
`component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组件路径',
`menu_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单类型(M目录 C菜单 F请求路径)',
`visible` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
`status` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
`icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单图标',
PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单表' ROW_FORMAT = Dynamic;
INSERT INTO `sys_menu` VALUES (1, 0, '查询操作', 0, '/sys/query', NULL, 'F', '0', '0', NULL);
INSERT INTO `sys_menu` VALUES (2, 0, '删除操作', 0, '/sys/delete', NULL, 'F', '0', '0', NULL);
SET FOREIGN_KEY_CHECKS = 1;
用户和角色关联表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_role_id` bigint(20) NOT NULL COMMENT '编号',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户编号',
`role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色编号',
PRIMARY KEY (`user_role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色关联表' ROW_FORMAT = Dynamic;
INSERT INTO `sys_user_role` VALUES (2, 1, 1);
INSERT INTO `sys_user_role` VALUES (4, 2, 2);
SET FOREIGN_KEY_CHECKS = 1;
角色和菜单关联表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_menu_id` bigint(20) NOT NULL COMMENT '角色菜单id',
`role_id` bigint(20) NOT NULL COMMENT '角色id',
`menu_id` bigint(20) NOT NULL COMMENT '菜单id',
PRIMARY KEY (`role_menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色菜单关联表' ROW_FORMAT = Dynamic;
INSERT INTO `sys_role_menu` VALUES (1, 1, 1);
INSERT INTO `sys_role_menu` VALUES (2, 1, 2);
INSERT INTO `sys_role_menu` VALUES (3, 2, 1);
SET FOREIGN_KEY_CHECKS = 1;
2.spring security核心配置文件
最重要的配置,像认证、授权、登入登出都在此处配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
9).集成swgger2
swgger2依赖加一下
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
注入swgger2配置
@Configuration
@EnableSwagger2
public class Swgger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger接口文档")
.description("Swagger-接口文档")
.version("1.0.0")
.build();
}
}
配置swgger2访问白名单,这个类为spring security 核心配置类
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] whitelist = {
, "/swagger-ui.html"
, "/swagger-resources/**"
, "/webjars/springfox-swagger-ui/**"
, "/v2/api-docs"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(whitelist).anonymous()
.anyRequest().authenticated()
;
}
}
启动服务在浏览器输入地址,在端口后面加上 /swagger-ui.html,出现以下页面代表成功
10).实体类和mybatis映射文件
①用戶表
SysUser.java
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="SysUser对象", description="用户表")
@TableName(value = "sys_user")
public class SysUser implements Serializable{
private static final long serialVersionUID = 1L;
@TableId
@ApiModelProperty(value = "用户编号")
private Long userId;
@ApiModelProperty(value = "用户名")
private String userName;
@ApiModelProperty(value = "用户密码")
private String pwd;
@ApiModelProperty(value = "邮箱")
private String email;
@ApiModelProperty(value = "性别")
private String sex;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "是否可用 0--可用 1--不可用")
private Integer userStatu;
@ApiModelProperty("上一次登录时间")
private Date lastLoginTime;
@TableField(exist = false)
private String token;
@TableField(exist = false)
private List<SysRole> roles;
}
SysUserDao.java
public interface SysUserDao extends BaseMapper<SysUser> {
SysUser queryOneLoginUser(String username);
}
SysUserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demobiz.corp.sys.user.dao.SysUserDao">
<resultMap id="BaseResultMap" type="com.example.demobiz.corp.sys.user.entity.SysUser">
<id column="user_id" property="userId" />
<result column="user_name" property="userName" />
<result column="pwd" property="pwd" />
<result column="email" property="email" />
<result column="sex" property="sex" />
<result column="create_time" property="createTime" />
<result column="user_statu" property="userStatu" />
<result column="last_login_time" property="lastLoginTime" />
</resultMap>
<resultMap id="userMap" type="com.example.demobiz.corp.sys.user.entity.SysUser">
<id column="user_id" property="userId" />
<result column="user_name" property="userName" />
<result column="pwd" property="pwd" />
<result column="email" property="email" />
<result column="sex" property="sex" />
<result column="create_time" property="createTime" />
<result column="user_statu" property="userStatu" />
<collection property="roles" resultMap="com.example.demobiz.corp.sys.role.dao.SysRoleDao.BaseResultMap"></collection>
</resultMap>
<select id="queryOneLoginUser" resultMap="userMap">
SELECT
u.user_id,
u.user_name,
u.pwd,
u.email,
u.sex,
u.create_time,
u.user_statu,
r.role_id,
r.`name`,
r.`code`,
r.remark,
r.role_statu
FROM
sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON r.role_id = ur.role_id
WHERE
r.role_statu =1 and user_name = #{username}
</select>
</mapper>
②角色表
SysRole.java
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="SysRole对象", description="角色表")
public class SysRole implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "编号")
private Long roleId;
@ApiModelProperty(value = "角色名")
private String name;
@ApiModelProperty(value = "角色编码")
private String code;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "角色状态 0--可用 1--不可用")
private Integer roleStatu;
}
SysRoleDao.java
public interface SysRoleDao extends BaseMapper<SysRole> {}
SysRoleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demobiz.corp.sys.role.dao.SysRoleDao">
<resultMap id="BaseResultMap" type="com.example.demobiz.corp.sys.role.entity.SysRole">
<id column="role_id" property="roleId" />
<result column="name" property="name" />
<result column="code" property="code" />
<result column="remark" property="remark" />
<result column="create_time" property="createTime" />
<result column="role_statu" property="roleStatu" />
</resultMap>
</mapper>
③菜单表
SysMenu.java
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="SysMenu对象", description="菜单表")
public class SysMenu implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "菜单id")
private Long menuId;
@ApiModelProperty(value = "父级id, 根菜单为0")
private Long parentId;
@ApiModelProperty(value = "菜单名")
private String menuName;
@ApiModelProperty(value = "排序")
private Integer srt;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "组件路径")
private String component;
@ApiModelProperty(value = "菜单类型(M目录 C菜单 F按钮)")
private String menuType;
@ApiModelProperty(value = "菜单状态(0显示 1隐藏)")
private String visible;
@ApiModelProperty(value = "菜单状态(0正常 1停用)")
private String status;
@ApiModelProperty(value = "菜单图标")
private String icon;
@TableField(exist = false)
private List<SysRole> roles;
}
SysMenuDao.java
public interface SysMenuDao extends BaseMapper<SysMenu> {
List<SysMenu> queryMenuRoles(String path);
}
SysMenuMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demobiz.corp.sys.menu.dao.SysMenuDao">
<resultMap id="BaseResultMap" type="com.example.demobiz.corp.sys.menu.entity.SysMenu">
<id column="menu_id" property="menuId" />
<result column="parent_id" property="parentId" />
<result column="menu_name" property="menuName" />
<result column="srt" property="srt" />
<result column="path" property="path" />
<result column="component" property="component" />
<result column="menu_type" property="menuType" />
<result column="visible" property="visible" />
<result column="status" property="status" />
<result column="icon" property="icon" />
</resultMap>
<resultMap id="queryMenuRolesMap" type="com.example.demobiz.corp.sys.menu.entity.SysMenu">
<id column="menu_id" property="menuId" />
<result column="parent_id" property="parentId" />
<result column="menu_name" property="menuName" />
<result column="srt" property="srt" />
<result column="path" property="path" />
<result column="component" property="component" />
<result column="menu_type" property="menuType" />
<result column="visible" property="visible" />
<result column="status" property="status" />
<result column="icon" property="icon" />
<collection property="roles" resultMap="com.example.demobiz.corp.sys.role.dao.SysRoleDao.BaseResultMap"></collection>
</resultMap>
<select id="queryMenuRoles" parameterType="string" resultMap="queryMenuRolesMap">
SELECT
m.menu_id,
m.menu_name,
m.path,
m.menu_type,
r.role_id,
r.`name`,
r.`code`
FROM
sys_menu m
INNER JOIN sys_role_menu rm ON m.menu_id = rm.menu_id
INNER JOIN sys_role r ON r.role_id = rm.role_id
where m.menu_type='F' and m.path= #{path}
</select>
</mapper>
2.登录认证
1).创建 UserDetailsServiceImpl并且根据用户名查询用户信息和对应的权限
这是用户认证的核心逻辑,创建UserDetailsServiceImpl并且实现UserDetailsService接口重写loadUserByUsername方法
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserDao dao;
@Override
public UserDetails loadUserByUsername(String username) throws InternalAuthenticationServiceException {
SysUser sysUser = dao.queryOneLoginUser(username);
if (sysUser == null) {
throw new InternalAuthenticationServiceException("用户名不存在");
}
List<String> list = new ArrayList<>();
for (SysRole role : sysUser.getRoles()) {
list.add(role.getCode());
}
return new LoginUser(sysUser,list);
}
}
2)登录用户信息
创建LoginUser实现UserDetails接口
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private SysUser sysUser;
private List<String> permissions;
@JSONField(serialize = false)
private List<GrantedAuthority> authorities;
public LoginUser(SysUser sysUser, List<String> permissions) {
this.sysUser = sysUser;
this.permissions = permissions;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
String[] strings = permissions.toArray(new String[permissions.size()]);
if (authorities != null) {
return authorities;
}
authorities = AuthorityUtils.createAuthorityList(strings);
return authorities;
}
@Override
public String getPassword() {
return sysUser.getPwd();
}
@Override
public String getUsername() {
return sysUser.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
3)配置认证管理且配置密码加密
核心配置中配置 WebSecurityConfig
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
注意这里几步出现StackOverflowError错误请参考
4)配置密码加密
新版本的Spring security规定必须设置一个默认的加密方式,不允许使用明文。这个加密方式是用于在登录时验证密码、注册时需要用到。 ????我们可以自己选择一种加密方式,Spring security为我们提供了多种加密方式,我们这里使用一种强hash方式进行加密。
在WebSecurityConfig 中注入(注入即可,不用声明使用),这样就会对提交的密码进行加密处理了,如果你没有注入加密方式,运行的时候会报错"There is no PasswordEncoder mapped for the id"错误。
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
5)创建SecurityUtils工具获取用户登录信息
public class SecurityUtils {
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
public static LoginUser getLoginUser(){
try {
return (LoginUser) getAuthentication().getPrincipal();
}catch (Exception e){
throw new RuntimeException("获取用户信息异常");
}
}
}
6)JWT令牌校验
①创建JwtAuthenticationTokenFilter
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
filterChain.doFilter(request, response);
return;
}
String uuid;
try {
Claims claims = JwtUtil.parseJWT(token);
uuid = claims.getSubject();
} catch (Exception e) {
request.setAttribute("filterError", new RuntimeException(ErrorCode.LOGIN_TIME_EXPIRED.getMessage()));
request.getRequestDispatcher("/error/throw").forward(request,response);
return;
}
String redisKey = "login:" + uuid;
LoginUser loginUser = redisCache.getCacheObject(redisKey);
if (Objects.isNull(loginUser)) {
request.setAttribute("filterError", new RuntimeException(ErrorCode.LOGIN_TIME_EXPIRED.getMessage()));
request.getRequestDispatcher("/error/throw").forward(request,response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
②过滤器异常重定向
因为过滤器抛出的异常全局异常捕抓不到所以创建ThrowError用于抛出异常
@Controller
public class ThrowError {
@RequestMapping("/error/throw")
public void rethrow(HttpServletRequest request) {
throw (RuntimeException) request.getAttribute("filterError");
}
}
③配置拦截
核心配置WebSecurityConfig
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
7)自定义登录策略
public class UserAndPwdLoginFilter extends UsernamePasswordAuthenticationFilter {
@Resource
private SessionRegistry sessionRegistry;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
String userName =authenticationBean.get("userName");
String pwd =authenticationBean.get("pwd");
if (userName == null) {
userName = "";
}
if (pwd == null) {
pwd = "";
}
request.setAttribute("userName",userName);
authRequest = new UsernamePasswordAuthenticationToken(
userName, pwd);
sessionRegistry.registerNewSession(request.getSession().getId(),authRequest.getPrincipal());
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
"", "");
}
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
else {
return super.attemptAuthentication(request, response);
}
}
}
核心配置WebSecurityConfig
@Resource
private SessionRegistry sessionRegistry;
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Autowired
private RedisCache redisCache;
@Bean
UserAndPwdLoginFilter userAndPwdSuccessFilter() throws Exception {
UserAndPwdLoginFilter filter = new UserAndPwdLoginFilter();
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String uuid= UUID.randomUUID().toString();
String jwt = JwtUtil.createJWT(uuid);
redisCache.setCacheObject("login:" + uuid, loginUser,10, TimeUnit.MINUTES);
WrapperResult<Object> result = WrapperResult.success(jwt, "登录成功");
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
});
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
WrapperResult result = null;
if (e instanceof AccountExpiredException) {
result = WrapperResult.fail(null, ErrorCode.USER_ACCOUNT_EXPIRED.getMessage());
} else if (e instanceof BadCredentialsException) {
result = WrapperResult.fail(null, ErrorCode.USER_CREDENTIALS_ERROR.getMessage());
} else {
result = WrapperResult.fail(null, e.getMessage());
}
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(result));
}
});
filter.setAuthenticationManager(authenticationManagerBean());
filter.setSessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry));
return filter;
}
configure加
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(userAndPwdSuccessFilter(), UsernamePasswordAuthenticationFilter.class);
}
8)用户未登录处理逻辑
创建CustomizeAuthenticationEntryPoint
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
WrapperResult wrapperResult = WrapperResult.fail(null, ErrorCode.LOGIN_TIME_EXPIRED.getMessage());
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(wrapperResult));
}
}
核心配置WebSecurityConfig
@Autowired
CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;
configure加
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(whitelist).anonymous()
.anyRequest().authenticated()
.and().exceptionHandling()
.authenticationEntryPoint(customizeAuthenticationEntryPoint)
;
3.授权
1)注解权限配置
①处理类
@Service("zhi")
public class Permission {
public boolean hasPermission(String permission) {
if (StringUtils.isEmpty(permission)) {
return false;
}
LoginUser loginUser= SecurityUtils.getLoginUser();
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
return false;
}
return loginUser.getPermissions().contains(permission);
}
}
②使用
调用delete需要admin角色权限
@RestController
@Api(tags = "用户信息")
@RequestMapping("/sys")
public class SysUserController {
@RequestMapping("/query")
public WrapperResult<Object> query(){
return WrapperResult.success(null, "query");
}
@PreAuthorize("@zhi.hasPermission('admin')")
@RequestMapping("/delete")
public WrapperResult<Object> delete(){
return WrapperResult.success(null, "delete");
}
}
2)授权失败逻辑处理配置
创建CustomAccessDeniedHandler
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
WrapperResult result = WrapperResult.fail(null, accessDeniedException.getMessage());
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(result));
}
}
核心配置WebSecurityConfig
@Autowired
CustomAccessDeniedHandler customAccessDeniedHandler;
configure
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(whitelist).anonymous()
.anyRequest().authenticated()
.and().exceptionHandling()
.authenticationEntryPoint(customizeAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
;
3)自定义权限拦截
注解配置权限固然方便,但是在实际开发中不可能在每个需要授权的方法上去配置,工作量大,同时在权限变更的时,还需要改动代码发版本,自定义权限拦截这是最好的处理方式,将权限配置存在数据库,然后拦截检查用户是否符合权限规则
①安全元数据源
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
private SysMenuDao sysMenuDao;
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) o).getRequestUrl();
List<SysMenu> sysMenus = sysMenuDao.queryMenuRoles(requestUrl);
if(CollectionUtils.isEmpty(sysMenus)){
return null;
}
List<SysRole> roles = sysMenus.get(0).getRoles();
String[] attributes = new String[roles.size()];
for(int i = 0;i<roles.size();i++){
attributes[i] = roles.get(i).getCode();
}
return SecurityConfig.createList(attributes);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
②访问决策管理器
@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = collection.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
String needRole = ca.getAttribute();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
③配置
核心配置WebSecurityConfig
@Autowired
CustomizeAccessDecisionManager accessDecisionManager;
@Autowired
CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;
configure加
http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);
o.setSecurityMetadataSource(securityMetadataSource);
return o;
}
});
4)退出登录逻辑处理
退出登录一般将session,redis,token删除或者失效
创建CustomizeLogoutSuccessHandler
@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private RedisCache redisCache;
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
String token = httpServletRequest.getHeader("token");
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
redisCache.deleteObject("login:"+userid);
} catch (Exception e) {
e.printStackTrace();
}
WrapperResult result = WrapperResult.success(null,"退出成功");
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
核心配置WebSecurityConfig
@Autowired
CustomizeLogoutSuccessHandler customizeLogoutSuccessHandler;
configure
http.logout()
.logoutSuccessHandler(customizeLogoutSuccessHandler)
.deleteCookies("JSESSIONID");
三.最终WebSecurityConfig的配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Resource
private SessionRegistry sessionRegistry;
@Autowired
private RedisCache redisCache;
@Autowired
CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;
@Autowired
CustomizeLogoutSuccessHandler customizeLogoutSuccessHandler;
@Autowired
CustomizeAccessDecisionManager accessDecisionManager;
@Autowired
CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
CustomAccessDeniedHandler customAccessDeniedHandler;
private static final String[] whitelist = {
"/login"
, "/swagger-ui.html"
, "/swagger-resources/**"
, "/webjars/springfox-swagger-ui/**"
, "/v2/api-docs"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(whitelist).anonymous()
.anyRequest().authenticated()
.and().exceptionHandling()
.authenticationEntryPoint(customizeAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
;
http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);
o.setSecurityMetadataSource(securityMetadataSource);
return o;
}
});
http.addFilterAfter(userAndPwdSuccessFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.logout()
.logoutSuccessHandler(customizeLogoutSuccessHandler)
.deleteCookies("JSESSIONID");
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
UserAndPwdLoginFilter userAndPwdSuccessFilter() throws Exception {
UserAndPwdLoginFilter filter = new UserAndPwdLoginFilter();
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String uuid= UUID.randomUUID().toString();
String jwt = JwtUtil.createJWT(uuid);
redisCache.setCacheObject("login:" + uuid, loginUser,10, TimeUnit.MINUTES);
WrapperResult<Object> result = WrapperResult.success(jwt, "登录成功");
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
});
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
WrapperResult result = null;
if (e instanceof AccountExpiredException) {
result = WrapperResult.fail(null, ErrorCode.USER_ACCOUNT_EXPIRED.getMessage());
} else if (e instanceof BadCredentialsException) {
result = WrapperResult.fail(null, ErrorCode.USER_CREDENTIALS_ERROR.getMessage());
} else if (e instanceof CredentialsExpiredException) {
result = WrapperResult.fail(null, ErrorCode.USER_CREDENTIALS_EXPIRED.getMessage());
} else if (e instanceof DisabledException) {
result = WrapperResult.fail(null, ErrorCode.USER_ACCOUNT_DISABLE.getMessage());
} else if (e instanceof LockedException) {
result = WrapperResult.fail(null, ErrorCode.USER_ACCOUNT_LOCKED.getMessage());
} else if (e instanceof InternalAuthenticationServiceException) {
result = WrapperResult.fail(null, ErrorCode.USER_ACCOUNT_NOT_EXIST.getMessage());
} else {
result = WrapperResult.fail(null, e.getMessage());
}
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(result));
}
});
filter.setAuthenticationManager(authenticationManagerBean());
filter.setSessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry));
return filter;
}
}
四.测试
1.登录过期或者未登录访问,需要认证请求
没传token,或者token过期或者redis存储过期
2.登录成功
账号密码正确并返回token信息
3.账号不存在
4.授权访问
携带token访问已授权的请求
访问未授权的请求
5.退出登录
将登录的token,调用退出登录
|