目录

1、环境搭建

1.1maven项目

1.2导入依赖

2.测试环境

3.实现安全认证

3.1原理

3.1.1实现UserDetailsService接口默认是从内存中获取密码,我们需要从数据库中来比对用户信息

3.2返回自定义结果集

3.3自定义实现类实现UserDetailsService接口

5.jwt的使用

5.1权限的验证

6,自定义认证异常和授权异常处理

7.配置跨域请求


1、环境搭建

1.1maven项目

1.2导入依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version></parent><dependencies><!--SpringSecurity启动器--><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.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.22</version></dependency></dependencies>

2.测试环境

@RestController
public class HelloController {@RequestMapping("/hello")public String hello(){return "hello";}
}

3.实现安全认证

3.1原理

3.1.1实现UserDetailsService接口默认是从内存中获取密码,我们需要从数据库中来比对用户信息

3.2返回自定义结果集

ResponseResult.class
package com.yrh.domain;import com.fasterxml.jackson.annotation.JsonInclude;@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {/*** 状态码*/private Integer code;/*** 提示信息,如果有错误时,前端可以获取该字段进行提示*/private String msg;/*** 查询到的结果数据,*/private T data;public ResponseResult(Integer code, String msg) {this.code = code;this.msg = msg;}public ResponseResult(Integer code, T data) {this.code = code;this.data = data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getData() {return data;}public void setData(T data) {this.data = data;}public ResponseResult(Integer code, String msg, T data) {this.code = code;this.msg = msg;this.data = data;}
}

数据库对应的实体类

用户信息

User.class
package com.yrh.domain;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;/*** 用户表(User)实体类** @author yrh*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class User implements Serializable {private static final long serialVersionUID = -40356785423868312L;/*** 主键*/@TableIdprivate Long id;/*** 用户名*/private String userName;/*** 昵称*/private String nickName;/*** 密码*/private String password;/*** 账号状态(0正常 1停用)*/private String status;/*** 邮箱*/private String email;/*** 手机号*/private String phonenumber;/*** 用户性别(0男,1女,2未知)*/private String sex;/*** 头像*/private String avatar;/*** 用户类型(0管理员,1普通用户)*/private String userType;/*** 创建人的用户id*/private Long createBy;/*** 创建时间*/private Date createTime;/*** 更新人*/private Long updateBy;/*** 更新时间*/private Date updateTime;/*** 删除标志(0代表未删除,1代表已删除)*/private Integer delFlag;
}

菜单权限列表

package com.yrh.domain;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;/*** 菜单表(Menu)实体类** @author makejava* @since 2021-11-24 15:30:08*/
@TableName(value="sys_menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Menu implements Serializable {private static final long serialVersionUID = -54979041104113736L;@TableIdprivate Long id;/*** 菜单名*/private String menuName;/*** 路由地址*/private String path;/*** 组件路径*/private String component;/*** 菜单状态(0显示 1隐藏)*/private String visible;/*** 菜单状态(0正常 1停用)*/private String status;/*** 权限标识*/private String perms;/*** 菜单图标*/private String icon;private Long createBy;private Date createTime;private Long updateBy;private Date updateTime;/*** 是否删除(0未删除 1已删除)*/private Integer delFlag;/*** 备注*/private String remark;
}

数据库sql

DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (`id` bigint(20) NOT NULL AUTO_INCREMENT,`menu_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '菜单名',`path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径',`visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',`perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识',`icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标',`create_by` bigint(20) NULL DEFAULT NULL,`create_time` datetime NULL DEFAULT NULL,`update_by` bigint(20) NULL DEFAULT NULL,`update_time` datetime NULL DEFAULT NULL,`del_flag` int(11) NULL DEFAULT 0 COMMENT '是否删除(0未删除 1已删除)',`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, '部门管理', 'dept', 'system/dept/index', '0', '0', 'system:dept:list', '#', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (2, '测试', 'test', 'system/test/index', '0', '0', 'system:test:list', '#', NULL, NULL, NULL, NULL, 0, NULL);-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色权限字符串',`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '角色状态(0正常 1停用)',`del_flag` int(1) NULL DEFAULT 0 COMMENT 'del_flag',`create_by` bigint(200) NULL DEFAULT NULL,`create_time` datetime NULL DEFAULT NULL,`update_by` bigint(200) NULL DEFAULT NULL,`update_time` datetime NULL DEFAULT NULL,`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'CEO', 'ceo', '0', 0, NULL, NULL, NULL, NULL, NULL);-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (`role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID',`menu_id` bigint(200) NOT NULL DEFAULT 0 COMMENT '菜单id',PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (1, 1);
INSERT INTO `sys_role_menu` VALUES (1, 2);-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '用户名',`nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '昵称',`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '密码',`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',`email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',`phonenumber` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',`avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',`user_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人的用户id',`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人',`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',`del_flag` int(11) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'zs', '张三', '$2a$10$C47dbXc.KlJyJq4Br50Mv.ujscDQmPqOcoXQc8khU70XZSsYv5j1G', '0', NULL, NULL, NULL, NULL, '1', NULL, NULL, NULL, NULL, 0);-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (`user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用户id',`role_id` bigint(200) NOT NULL DEFAULT 0 COMMENT '角色id',PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);

3.3自定义实现类实现UserDetailsService接口

步骤

  1. 实现loadUserByUsername方法
  2. 通过用户名获取用户信息
  3. 通过用户id获取用户权限信息
  4. 返回UserDetails对象
  5. 导入依赖

导入依赖

        <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency>

application.yml

spring:datasource:url: jdbc:mysql://localhost:3306/security?characterEncoding=utf-8&serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
server:port: 8888

mapper

UserMapper用户数据层接口

package com.yrh.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yrh.domain.User;/*** @author: yrh* @ClassName: UserMapper* @Description: TODO* @Date: Created in 20:59 2022/3/4* @Version 1.0*/
public interface UserMapper extends BaseMapper<User> {
}
MenuMapper权限列表接口
package com.yrh.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yrh.domain.Menu;import java.util.Set;/*** @author: yrh* @ClassName: MenuMapper* @Description: TODO* @Date: Created in 22:41 2022/3/5* @Version 1.0*/
public interface MenuMapper extends BaseMapper<Menu> {//根据id查询用户权限访问列表Set<String> selectPermsByUserId(Long userId);
}

MenuMapper.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.yrh.mapper.MenuMapper"><select id="selectPermsByUserId" resultType="java.lang.String">SELECTDISTINCT m.`perms`FROMsys_user_role urLEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`WHEREuser_id = #{userid}AND r.`status` = 0AND m.`status` = 0</select>
</mapper>

实现loadUserByUsername

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate MenuMapper menuMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserName,s);User user = userMapper.selectOne(queryWrapper);//非空判断if (Objects.isNull(user)){throw new RuntimeException("用户不存在");}//查询对应的用户信息和权限信息Set<String> permissions = menuMapper.selectPermsByUserId(user.getId());return new LoginUser(user,permissions);}
}

该方法返回的结果是UserDetails 对象我们需要继承该对象

LoginUser.class

@Data
@AllArgsConstructor
public class LoginUser implements UserDetails {private User user;private Set<String> permissions;public LoginUser(User user, Set<String> permissions) {this.user = user;this.permissions = permissions;}@JSONField(serialize = false)private Set<SimpleGrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if (authorities!=null){return authorities;}authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());return authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

注意getAuthorities是获取权限列表的方法,不需要继续序列化,直接从缓存中获取加上  @JSONField(serialize = false)注解

4、用户登录进去权限的认证和授权

步骤

  1. 前端调用登录接口传入登录参数
  2. UsernamePasswordAuthenticationToken接口中存放客服端传入的参数按照重写的loadUserByUsername和密码加密规则继续比对
  3. AuthenticationManager进行用户认证,认证通过获取存入的用户对象信息
  4. 用户用户的id,通过用户的id生成一个jwt
  5. 把用户信息存入到redis中
  6. 把登录状态和jwt返回给前端

工具类

RedisCache 对redis操作的一些封装
@Component
public class RedisCache
{@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public long deleteObject(final Collection collection){return redisTemplate.delete(collection);}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/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;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 删除Hash中的数据* * @param key* @param hkey*/public void delCacheMapValue(final String key, final String hkey){HashOperations hashOperations = redisTemplate.opsForHash();hashOperations.delete(key, hkey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}
}
JwtUtil.class

对jwt操作的工具类例如从jwt中解析userId等

/*** JWT工具类*/
public class JwtUtil {//有效期为public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时//设置秘钥明文public static final String JWT_KEY = "sangeng";public static String getUUID(){String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}/*** 生成jtw* @param subject token中要存放的数据(json格式)* @return*/public static String createJWT(String subject) {JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间return builder.compact();}/*** 生成jtw* @param subject token中要存放的数据(json格式)* @param ttlMillis token超时时间* @return*/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)              //唯一的ID.setSubject(subject)   // 主题  可以是JSON数据.setIssuer("sg")     // 签发者.setIssuedAt(now)      // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);}/*** 创建token* @param id* @param subject* @param ttlMillis* @return*/public static String createJWT(String id, String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间return builder.compact();}public static void main(String[] args) throws Exception {
//        String jwt = createJWT("2123");Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0");String subject = claims.getSubject();System.out.println(subject);
//        System.out.println(claims);}/*** 生成加密后的秘钥 secretKey* @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}
Redis使用FastJson序列化工具类
FastJsonRedisSerializer
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;}@Overridepublic byte[] serialize(T t) throws SerializationException{if (t == null){return new byte[0];}return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);}@Overridepublic 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);}
}
WebUtils 用户返回给前端对应信息的工具类
public class WebUtils
{/*** 将字符串渲染到客户端* * @param response 渲染对象* @param string 待渲染的字符串* @return null*/public static String renderString(HttpServletResponse response, String string) {try{response.setStatus(200);response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().print(string);}catch (IOException e){e.printStackTrace();}return null;}
}

配置类

RedisConfig
@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;}
}
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()
//                .antMatchers("/testCors").hasAuthority("system:dept:list222")// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();}
}

导入依赖

   <!--redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--fastjson依赖--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.33</version></dependency><!--jwt依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency>

步骤一:

登录接口

@RestController
public class LoginController {@Autowiredprivate LoginService loginService;/*** @author: yrh* @Description: 用户登录接口* @return: null*/@PostMapping("/user/login")public ResponseResult login(@RequestBody User user){return loginService.login(user);}}

接口方法编写

public interface LoginService {//登录接口public ResponseResult login(User user);/*** @author: yrh* @Description: 退出接口* @return: null*/ResponseResult logout();
}

接口实现类编写

LoginServiceImpl.class
@Service
public class LoginServiceImpl implements LoginService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisCache redisCache;@Overridepublic ResponseResult login(User user) {//AuthenticationManager authenticate进行用户认证UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);//是否为空if (Objects.isNull(authenticate)) {throw new RuntimeException("登录失败");}//如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回LoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userId = loginUser.getUser().getId().toString();//返回给前端的jwtString jwt = JwtUtil.createJWT(userId);//存入到redis中redisCache.setCacheObject("login:" + userId, loginUser);HashMap<String, Object> token = new HashMap<>();token.put("token", jwt);return new ResponseResult(200, "登录成功", token);}}

步骤二

UsernamePasswordAuthenticationToken接口中存放客服端传入的参数按照重写的loadUserByUsername和密码加密规则继续比对

在SecurityConfig中配置加密规则

    //创建BCryptPasswordEncoder注入容器@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
步骤三AuthenticationManager进行用户认证,认证通过获取存入的用户对象信息

在SecurityConfig中添加认证

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();
}

步骤四五六在步骤三成功后执行

用postman进行登录接口测试

5.jwt的使用

场景运用

前端请求接口的接口的时候需要携带token信息,我们在用户认证之前应该对先拦截请求,从请求头中看是否携带了token,验证token信息决定是否放行,需要注意对登录方法放行

步骤一

自定义一个拦截器用来拦截用户请求

步骤二

判断是否携带token未携带则放行

步骤三

解析token获取用户userId

步骤四

通过uersId获取redis中的对象信息判断是否存在

步骤五

把用户信息和权限信息存入到Authentication中

步骤六在securityConfig中配置Filter

JwtAuthenticationTokenFilter.class
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取tokenString token = request.getHeader("token");//判断是否携带tokenif (!StringUtils.hasText(token)) {//为空放行filterChain.doFilter(request, response);return;}//解析tokenString userId;try {Claims claims = JwtUtil.parseJWT(token);userId = claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new RuntimeException("token出错");}//从redis中获取对象信息String redisKey = "login:" + userId;LoginUser user = redisCache.getCacheObject(redisKey);if (Objects.isNull(user)) {throw new RuntimeException("用户未登录");}//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request, response);}
}

securityConfig中配置Filter

指定在UsernamePasswordAuthenticationFilter过滤器之前进行拦截

 //jwt过滤器@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()
//                .antMatchers("/testCors").hasAuthority("system:dept:list222")// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);}

测试

未携带token

携带token

5.1权限的验证

@PreAuthorize("hasAuthority('system:dept:list')")

只需要在方法上加入这个注解里面添加的system:dept:list字符串会根据对应的表达式规则进行权限的验证,该方法通过获取Authentication中的权限列表进行比对,满足条件返回ture否在flase,

@RestController
public class HelloController {@RequestMapping("/hello")@PreAuthorize("hasAuthority('system:dept:list')")public String hello(){return "hello";}
}

数据库对应该用户的权限

6,自定义认证异常和授权异常处理

认证异常结果处理实现AuthenticationEntryPoint接口把异常结果响应给前端

AuthenticationEntryPointImpl.class
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {//自定义返回结果集ResponseResult responseResult=new ResponseResult(HttpStatus.UNAUTHORIZED.value(),"用户认证失败请查询登录");String json = JSON.toJSONString(responseResult);//响应前台异常信息WebUtils.renderString(response,json);}
}

授权异常处理

实现AccessDeniedHandler接口

AccessDeniedHandlerImpl.class
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {//自定义返回结果集ResponseResult responseResult=new ResponseResult(HttpStatus.FORBIDDEN.value(),"您的权限不足");String json = JSON.toJSONString(responseResult);//响应前台异常信息WebUtils.renderString(response,json);}
}

在SecurityConfig中配置异常信息处理

 //认证异常@Autowiredprivate AuthenticationEntryPoint authenticationEntryPoint;//授权异常@Autowiredprivate AccessDeniedHandler accessDeniedHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()
//                .antMatchers("/testCors").hasAuthority("system:dept:list222")// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);//配置异常处理器;http.exceptionHandling()//配置授权失败处理器.accessDeniedHandler(accessDeniedHandler).//配置认证失败authenticationEntryPoint(authenticationEntryPoint);}

7.配置可以请求

在前后端分离项目中必然存在跨域请求问题我们在spingBoot中进行配置然后在配置在securityConfig中

CorsConfig.class 跨域请求配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**")// 设置允许跨域请求的域名.allowedOriginPatterns("*")// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods("GET", "POST", "DELETE", "PUT")// 设置允许的header属性.allowedHeaders("*")// 跨域允许时间.maxAge(3600);}
}

在securityConfig进行配置

 @Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()
//                .antMatchers("/testCors").hasAuthority("system:dept:list222")// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);//配置异常处理器;http.exceptionHandling()//配置授权失败处理器.accessDeniedHandler(accessDeniedHandler).//配置认证失败authenticationEntryPoint(authenticationEntryPoint);//允许跨域http.csrf();}

//允许跨域
        http.csrf();

SpringSecurity+jwt安全框架相关推荐

  1. Springboot+Spring-Security+JWT 实现用户登录和权限认证

    如今,互联网项目对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,目前比较成熟的几种大家比较熟悉的模式,像RBAC 基于角色权限的验证,shiro框架专门用于处理权限方面的,另一个比较流行的 ...

  2. SpringSecurity+JWT实现登陆验证的思路(有一点点源码分析)

    看了几个SpringSecurity+JWT的登陆demo,两个demo在一些细节实现上有一些不同,然后对于各个类和接口的关系比较模糊,就决定整理一下思路. 先简单的借用一下一位UP 三更草堂的图了解 ...

  3. Java项目:在线淘房系统(租房、购房)(java+SpringBoot+Redis+MySQL+Vue+SpringSecurity+JWT+ElasticSearch+WebSocket)

    源码获取:博客首页 "资源" 里下载! 该系统有三个角色,分别是:普通用户.房屋中介.管理员.普通用户的功能:浏览房屋信息.预约看房.和中介聊天.申请成为中介等等.房屋中介的功能: ...

  4. SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

    文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...

  5. SpringSecurity + JWT,从入门到精通!

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 链接:22j.co/bBbq 权限系统躲不开的概念,在Shiro和Spri ...

  6. SpringSecurity + JWT实现单点登录

    前面说了很多的理论知识,本文使用SpringSecurity + JWT来实现单点登录. 文章目录 什么是单点登陆 简单的运行机制 SpringSecurity整合JWT 认证思路分析 什么是单点登陆 ...

  7. 前后端分离的用户验证原理及Spring Boot + JWT的框架搭建(附完整的框架代码)之二

    本篇承接上一篇,关于Session以及JWT Token参考: 前后端分离的用户验证原理及Spring Boot + JWT的框架搭建(附完整的框架代码)之一 框架整体描述 框架使用Spring Bo ...

  8. html jwt权限控制,SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制

    在整合jwt之前,我们首先要在SpringBoot中整合security的模块,来实现基于security的授权控制.用过security的人都知道,它的功能无比的强大比shiro还要强大,但是今天我 ...

  9. Java权限管理|基于springBoot+springSecurity+jwt实现前后端分离用户权限认证

    基于springBoot+springSecurity+jwt实现前后端分离用户权限认证 1. 项目说明   主要基于前后端分离情况下用户权限认证, 当用户登录认证成功后,每个用户会获取到自己的tok ...

最新文章

  1. Linux系统自动更新时间
  2. 量子计算机是程序员的未来,研究者:量子计算机一旦成功问世,时间也许会失去存在的意义...
  3. 无向图的深度优先遍历非递归_【数据结构图(一)】什么是图
  4. MFC中文件打开与保存
  5. python模块 - functools模块
  6. getwmi php 报错,调用win32_service类就报错“get-wmiobject :常规故障”,这是wmi类损坏?...
  7. python数据分析课程网盘-数据分析技能 全套 百度网盘 下载
  8. CPC客户端报错 error
  9. arm-linux-g++ crypto,在Ubuntu中找不到libcrypto
  10. 备份VMWare ESXi虚拟机
  11. 使用Word制作签名电子版
  12. 2009.9.13 网摘总结
  13. SOFA Weekly | SOFA 社区元旦快乐,MOSN 荣获 2020 中国优秀开源项目
  14. webService和WebApi的区别
  15. (新)Chrome浏览器自定义背景插件
  16. python打字机效果_如何在电脑键盘打字的时候有打字机的声音效果?
  17. 如何编制项目蓝图汇报材料
  18. Delivering Smiles:亚马逊的温暖进行时
  19. class二进制文件解析(一)
  20. 什么是机械学习?及Scikit-learn机械学习库

热门文章

  1. Oracle数据库管理系统(安装及入门教学)
  2. Python练习题018:a+aa+aaa+……
  3. [windows优化]win10折腾过程
  4. java责任链模式审批请假_14-学生生病请假:责任链模式
  5. (转)一个大牛的acm历程(看着就要颤抖)
  6. element-ui 表格使用多选 如何回显打勾
  7. Ping计算机名和Ping网站域名都是由DNS解析吗?
  8. JS获取yyyy-MM-dd HH:mm:ss格式的时间
  9. Linux 账号与身份管理2
  10. VUE div click无效