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,"退出成功");

}

```

相关推荐
岁月变迁呀3 小时前
Redis梳理
数据库·redis·缓存
Code apprenticeship5 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站5 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶5 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
黄名富9 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
G_whang10 小时前
centos7下docker 容器实现redis主从同步
redis·docker·容器
.生产的驴10 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
我叫啥都行13 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
阿乾之铭14 小时前
Redis四种模式在Spring Boot框架下的配置
redis
on the way 12316 小时前
Redisson锁简单使用
redis