0.简介
是spring家族中安全管理框架
认证:验证当前访问系统的是不是本系统的用户,并且确认具体是那个用户
授权:经过认证后判断当前用户是否有某个权限进行操作
1.快速入门
1.1搭建一个简单的springboot项目
1.设置父工程添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.GuoYao</groupId> <artifactId>GuoYaoSecurityQuickStart</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
启动类
package com.guoyao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SecurityApplication { public static void main(String[] args) { SpringApplication.run(SecurityApplication.class,args); } }
controller在启动类所在包或其子包下
package com.guoyao.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "Hello"; } }
1.2引入security依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2.认证
2.1登录校验流程
2.2.1流程2.2.2认证流程
2.3解决问题
2.3.1
1.自定义登录接口调用providerManager方法进行认证(希望使用特定的身份验证方式,而不是Spring Security默认的用户名密码认证方式。)
调用providerManager中的方法进行认证,如果认证通过生成jwt,根据id将用户信息存入redis中
2.自定义userdetailservice在这个实现类中查询数据库
校验:
1.定义jwt认证过滤器
获取token(jwt)(JWT(JSON Web Token)就是一种令牌(Token)的实现方式之一。令牌是一种用于身份验证和授权的安全标识,它包含了有关用户或客户端的信息,以及相关的权限信息。)
解析token获取期中的userId
从redis获取用户信息
存入securityContrextHolder
序列化器防止乱码
package com.guoyao.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
* @author 35238
* @date 2023/7/11 0011 15:37
*/
//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);
}
}
工具类和配置类在软件开发中有不同的作用和使用场景:
-
工具类 (Utility Class):上面的
- 工具类通常包含一组静态方法,用于执行特定的功能操作,如字符串处理、日期处理、数学计算等。
- 工具类的方法通常是无状态的,即不依赖于对象的实例状态,可以直接调用,不需要创建对象实例。
- 工具类中的方法通常是通用性的,可以在不同的应用程序或模块中复用。
- 典型的例子包括
StringUtils
、DateUtils
、MathUtils
等。
-
配置类 (Configuration Class):
- 配置类通常用于配置应用程序的各种组件、服务、依赖项等。
- 在Spring框架中,配置类通常使用
@Configuration
注解进行标记,用于定义Bean的创建和配置。 - 配置类中可以包含方法,用于创建和配置Bean实例,这些方法通常使用
@Bean
注解进行标记。 - 配置类也可以包含其他配置相关的注解,如
@ComponentScan
、@Import
等,用于进一步配置应用程序的行为。 - 典型的例子包括数据库连接配置类、缓存配置类、Spring Security配置类等。
总体来说,工具类用于封装通用的功能方法,而配置类用于配置应用程序的各种组件和行为。在实际开发中,它们各自都有不同的作用和价值,并且通常会在项目中同时存在。
配置类调用工具类
FastJsonRedisSerializer
是一个自定义的Redis序列化器,使用FastJson进行对象的序列化和反序列化,而RedisConfig
是一个Spring配置类,用于配置Spring应用程序中的RedisTemplate,并设置了适当的序列化器。这样可以确保应用程序可以正确地与Redis进行交互,并且能够以JSON格式存储和检索对象数据。
package com.guoyao.config; import com.guoyao.utils.FastJsonRedisSerializer; 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; /** * @author 35238 * @date 2023/7/11 0011 15:40 */ @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; } }
响应类
package com.guoyao.domain; import com.fasterxml.jackson.annotation.JsonInclude; /** * @author 35238 * @date 2023/7/11 0011 15:43 */ //响应类 @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; } }
生成jwt并且对jwt进行解析
package com.guoyao.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID; /** * @author 35238 * @date 2023/7/11 0011 15:46 */ //JWT工具类 public class JwtUtil { //有效期为 public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时 //设置秘钥明文, 注意长度必须大于等于6位 public static final String JWT_KEY = "huanfqc"; 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("huanf") // 签发者 .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 token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg"; Claims claims = parseJWT(token); 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(); } }
package com.guoyao.utils; 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; /** * @author 35238 * @date 2023/7/11 0011 15:50 */ @SuppressWarnings(value = { "unchecked", "rawtypes" }) @Component //redis工具类 public class RedisCache { @Autowired public 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); } }
将字符串渲染到客户端
package com.huanf.utils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/**
* @author 35238
* @date 2023/7/11 0011 15:53
*/
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;
}
}
2.3.3自定义数据库
create database if not exists huanf_security;
use huanf_security;CREATE TABLE `sys_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
`nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',
`password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',
`status` CHAR(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
`email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱',
`phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手机号',
`sex` CHAR(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
`avatar` VARCHAR(128) DEFAULT NULL COMMENT '头像',
`user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',
`create_by` BIGINT(20) DEFAULT NULL COMMENT '创建人的用户id',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`del_flag` INT(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';insert into sys_user values (1,'admin','管理员','{noop}123456','0',DEFAULT,DEFAULT,DEFAULT,DEFAULT,'0',DEFAULT,DEFAULT,DEFAULT,DEFAULT,DEFAULT);
insert into sys_user values (2,'huanf','涣沷a靑惷','{noop}112233','0',DEFAULT,DEFAULT,DEFAULT,DEFAULT,'1',DEFAULT,DEFAULT,DEFAULT,DEFAULT,DEFAULT);
引入mybatis-plus依赖
<!--引入MybatisPuls依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <!--引入mysql驱动的依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
在resources创建file application.yml配置数据库连接信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/huanf_security?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 228675
driver-class-name: com.mysql.cj.jdbc.Driver
实体类user
package com.guoyao.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; /** * @author 35238 * @date 2023/7/11 0011 16:00 */ //用户表(User)实体类 @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -40356785423868312L; /** * 主键 */ @TableId private 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; }
要用测试类要引入junit依赖
<!--引入Junit,用于测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
package com.guoyao.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.guoyao.domain.LoginUser; import com.guoyao.domain.User; import com.guoyao.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import java.util.Objects; @Service public class UserDetailsServiceImpl implements UserDetailsService { //查询用户信息 @Autowired private UserMapper userMapper; @Override public 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("用户名或密码错误"); } //TODO 查询权限信息 //吧数据封装成Userdetails实现 return new LoginUser(user); } }
package com.guoyao.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; @Data @NoArgsConstructor @AllArgsConstructor public class LoginUser implements UserDetails { private User user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.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; } }
去掉铭文密码前面的{noop}
package com.guoyao.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean //创建BCryptPasswordEncoder注入容器 public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
过滤器可以进行泛型
2.3.3.3登录接口
我们需要自定义登录接口
让springsecurity对这个接口放行,用户访问登录接口时不用验证
在接口中,通过AuthenManager的authenticate方法来进行认证,所以需要吧在securityConfig中配置将其注入容器
认证成功的话生成jwt,放入响应中并返回,将id作为键,信息作为值存入redis,存入rediscash(里面注入了redisconfig序列化配置返回的值),
@Bean
注解的作用是告诉Spring容器需要创建并管理被注解的方法返回的对象,并且这些对象可以在应用的其他地方被引用和使
package com.guoyao.service.impl; import com.guoyao.domain.LoginUser; import com.guoyao.domain.ResponseResult; import com.guoyao.domain.User; import com.guoyao.service.LoginService; import com.guoyao.utils.JwtUtil; import com.guoyao.utils.RedisCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.Objects; @Service public class LoginServiceImpl implements LoginService { //从容器当中自动注入,前面重写方法放到了bean容器中 @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public ResponseResult login(User user) { //authenticate认证 //用户登录是的密码封装成了对象 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); //会自动调用UserDetails //如果认证通过 ,使用userid生成jwt jwt存入responseresult返回jwt给前端 if (Objects.isNull(authenticate)) { throw new RuntimeException("登录失败"); } //返回认证主体 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userid = loginUser.getUser().getId().toString(); String jwt = JwtUtil.createJWT(userid); redisCache.setCacheObject("login"+userid,loginUser); //把完整的用户信息存入redis Map<String, String> map = new HashMap<>(); map.put("token", jwt); return new ResponseResult(200, "登陆成功", map); //如果认证通过 ,使用userid生成jwt jwt存入responseresult返回jwt给前端 //将完整的用户信息存入redis } }
hello进不去,redis存入信息错误,login:
通常在使用Redis作为缓存时,需要配置RedisTemplate的序列化方式,以确保对象能够正确地被序列化和反序列化。在你提供的代码中,没有看到RedisTemplate的序列化配置,但假设你在你的Redis配置类中进行了这样的配置。
一般来说,在Spring Boot应用程序中,你可以创建一个Redis配置类,例如RedisConfig,在该类中配置RedisTemplate的序列化方式。你可能会使用Jackson2JsonRedisSerializer或者其他适合你应用需求的序列化器。
loginuser继承userdetails,从数据库获取
授权
alt+insert调出类里面的可重写方法
package com.guoyao.config; import com.guoyao.filter.JwtAuthenticationTokenFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication; 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.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { //判断进入是是否携带token @Bean //创建BCryptPasswordEncoder注入容器 public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Autowired //注入我们在filter目录写好的类 private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; //便于比对后面修改登录进去的密码 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } //对于登录接口放行 @Override protected void configure(HttpSecurity http) throws Exception { http //由于是前后端分离项目,所以要关闭csrf .csrf().disable() //由于是前后端分离项目,所以session是失效的,我们就不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() //指定让spring security放行登录接口的规则 .authorizeRequests() // 对于登录接口 anonymous表示允许匿名访问 .antMatchers("/user/login").anonymous() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } }
rbac基于角色的权限控制
异常处理,
登录 403因为路径写错,没有权限
401用户名或密码错误
@Component注入到spring容器里了
- Server: 服务器的地址或主机名,可以是IP地址或域名。
- Port: 端口号是用于标识应用程序通信的数字。它允许一台计算机上的多个网络服务在同一时间运行,每个服务通过唯一的端口号与网络进行通信。
处理异常配置
目录