SpringSecurity(JWT、SecurityConfig、Redis)

可能会报错Last unit does not have enough valid bits

把jwtUtils的KEY改成偶数位

主要用于校验,授权

导入依赖

<dependency)

<groupId>org.springframework. boot</groupId>

<artifactId>spring-boot-starter-security</artifactId)

</dependency>

导入以来会自动跳转登录页面,用户为user,密码在控制台,也就是打印日志记录

它的原理就是一个过滤器链,一个过滤器,多个就是过滤器链。

这里springboot版本用的是2.5.0

jdk版本也不要搞错了,一定是java8

fastjson是用于json转换的工具

<!--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>

关于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配置

```

@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;

}

}

```

统一返回值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;

}

}

```

JWT工具包

```

public class JwtUtil {

//有效期为

public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时

//设置秘钥明文

public static final String JWT_KEY = "fancy";

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("fancy") // 签发者

.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();

}

}

```

RedisCache

```

package com.imot.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;

@SuppressWarnings(value = { "unchecked", "rawtypes" })

@Component

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);

}

}

```

WebUtils

```

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

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;

}

}

```

这里注意如果在数据库中的密码前面加上{noop}即可让密码明文

重写一个UserDetailsServiceImpl去实现UserDetailsService重写方法loadUserByUsername,返回值是UserDetails

```

@Service

public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired

private UserMapper userMapper;

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//查询用户信息

LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();

userLambdaQueryWrapper.eq(User::getUserName,username);

User user = userMapper.selectOne(userLambdaQueryWrapper);

if (Objects.isNull(user)){

throw new RuntimeException("用户名或密码错误");

}

//TODO 查询对应的权限信息

```

创建一个类LoginUser来实现返回值UserDetails

```

package com.imot.pojo.entity;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Data

@AllArgsConstructor

@NoArgsConstructor

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;

}

}

```

加密方法为

BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

String encode = passwordEncoder.encode("1234");

passwordEncoder.matches("明文","密文"); //可查看是否相等返回一个布尔值

```

```

因为已经封装好Bean的SecurityConfig,需要调用直接autowaired

jwt依赖

```

<!--jwt依赖-->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>0.9.0</version>

</dependency>

```

**SecurityConfig**

```

package com.imot.config;

import com.imot.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.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

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean

public PasswordEncoder passwordEncoder(){

return new BCryptPasswordEncoder();

}

@Autowired

private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Override

protected void configure(HttpSecurity http) throws Exception {

http

//关闭csrf

.csrf().disable()

//不通过Session获取SecurityContext

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

.and()

.authorizeRequests()

// 对于登录接口 允许匿名访问

.antMatchers("/user/login").anonymous()

// 除上面外的所有请求全部需要鉴权认证

.anyRequest().authenticated();

http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

}

@Override

@Bean

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

}

```

jwtUtils工具中

createJWt("123")就是生成token的方法

这就是解析token,subject即是解密

Claims claims= parseJT("eyJhbGci0iJ");

String subject = claims.getSubject();

在SecurityConfig中重写方法authenticationManagerBean,按住alt+insert

**写一个登录的方法**

```

@PostMapping("/user/login")

@ApiOperation("测试登录")

public ResponseResult login(@RequestBody User user) {

//登录

return loginService.login(user);

}

```

**LoginServiceImpl**

```

package com.imot.service.impl;

import com.imot.domain.ResponseResult;

import com.imot.pojo.entity.LoginUser;

import com.imot.pojo.entity.User;

import com.imot.service.LoginService;

import com.imot.utils.JwtUtil;

import com.imot.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.Objects;

@Service

public class LoginServiceImpl implements LoginService {

@Autowired

private AuthenticationManager authenticationManager;

@Autowired

private RedisCache redisCache;

@Override

public ResponseResult login(User user) {

//AuthenticationManager进行用户认证

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());

Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);

//判断null就验证没有通过,不为null就认证通过了

if (Objects.isNull(authenticate)){

throw new RuntimeException("登陆失败");

}

//如果认证通过了,使用userid去生成一个jwt存入返回值responseResult返回

//使用userid生成token

LoginUser loginUser = (LoginUser) authenticate.getPrincipal();

String userId = loginUser.getUser().getId().toString();

String jwt = JwtUtil.createJWT(userId);

//authenticate存入redis

redisCache.setCacheObject("login:"+userId,loginUser);

//把token响应给前端

HashMap<String,String> map = new HashMap<>();

map.put("token",jwt);

return new ResponseResult(200,"登陆成功",map);

//

}

}

```

Jwt过滤器

```

package com.imot.filter;

import com.imot.pojo.entity.LoginUser;

import com.imot.utils.JwtUtil;

import com.imot.utils.RedisCache;

import io.jsonwebtoken.Claims;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.stereotype.Component;

import org.springframework.util.StringUtils;

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.util.Objects;

@Component

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

@Autowired

private RedisCache redisCache;

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

//获取token

String token = request.getHeader("token");

if (!StringUtils.hasText(token)) {

//放行

filterChain.doFilter(request, response);

return;

}

//解析token

String 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 loginUser = redisCache.getCacheObject(redisKey);

if(Objects.isNull(loginUser)){

throw new RuntimeException("用户未登录");

}

//存入SecurityContextHolder

//TODO 获取权限信息封装到Authentication中

UsernamePasswordAuthenticationToken authenticationToken =

new UsernamePasswordAuthenticationToken(loginUser,null,null);

SecurityContextHolder.getContext().setAuthentication(authenticationToken);

//放行

filterChain.doFilter(request, response);

}

}

```

退出controller

```

@RequestMapping("/user/logout")

@ApiOperation("测试退出")

public ResponseResult logout(@RequestBody User user) {

//登录

return loginService.logout();

}

```

serviceImpl

```

@Override

public ResponseResult logout() {

UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();

LoginUser loginUser = (LoginUser) authentication.getPrincipal();

Long userid = loginUser.getUser().getId();

redisCache.deleteObject("login:"+userid);

return new ResponseResult(200,"退出成功");

}

```

相关推荐
CoderJia程序员甲3 小时前
重学SpringBoot3-集成Redis(一)
java·redis·缓存·springboot
speop3 小时前
408笔记|随笔记录|自用|2
java·redis·笔记
王维诗里的代码i4 小时前
Redis基础二(spring整合redis)
java·数据库·redis·spring
技术卷4 小时前
Redis数据库与GO完结篇:redis操作总结与GO使用redis
数据库·redis·golang
盒马盒马4 小时前
Redis:list类型
数据库·redis
wclass-zhengge5 小时前
Redis篇(Redis原理 - 网络模型)
网络·数据库·redis
周周写不完的代码5 小时前
redis-数据类型
数据库·redis·缓存
JarvisO9 小时前
SpringBoot+Redis+RabbitMQ完成增删改查
spring boot·redis·java-rabbitmq
weixin_4381973811 小时前
linux部署redis,整合ansible和redis
linux·redis·git
周周写不完的代码15 小时前
Redis-持久化机制
数据库·redis·缓存