目录
一、依赖
二、逻辑图
三、代码设计
1、WebSecurityConfigurerAdapter的实现类
2、设计登录接口
config配置:
1)UserDetailsService实现类重写:
2)书写登录实现类(调用authenticationManager、可以与后面的逻辑连贯起来)
四、认证加授权
五、工具类:(可网上随便找了用)
jwt
redis
redis序列化:
六、逻辑梳理
1)security有个Authentication
2)密码校验
一、依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId></dependency>
// <dependency>
// <groupId>org.springframework.cloud</groupId>
// <artifactId>spring-cloud-starter-oauth2</artifactId>
// </dependency>
二、逻辑图
图1
图2
三、代码设计
1、WebSecurityConfigurerAdapter的实现类
package com.example.config;import com.example.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.EnableWebSecurity;
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;/*** @author Mr.M* @version 1.0* @description 安全管理配置* @date 2022/9/26 20:53*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) //开启资源授权配置 权限和资源是两码事
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {// @Autowired JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//配置用户信息服务
// @Bean
// public UserDetailsService userDetailsService() {
// //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
// manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
// return manager;
// }
//@AutowiredJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//引入Bean AuthenticationManager 供调用@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic PasswordEncoder passwordEncoder() {
// //密码为明文方式
// return NoOpPasswordEncoder.getInstance()//spring用于加密的一个算法return new BCryptPasswordEncoder();}//配置安全拦截机制//__________________这里的所有路径都是不包含上下文路径的@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/login").anonymous()//放行接口 未登录才能访问.antMatchers().permitAll()//.anonymous()未登录 已登录不可访问//.authenticated()已登录//.permitAll()未登录已登录都可访问//接口权限配置.antMatchers("/getUser/*").hasAnyAuthority("getUser2").anyRequest().authenticated();//其它全部需要认证
// .and()
// .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success//认证过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//security允许跨域http.cors();}}
@Bean public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean(); }将AuthenticationManager注入bean 实现自己设计的登录接口与auth流程的衔接
@Beanpublic PasswordEncoder passwordEncoder() { // //密码为明文方式 // return NoOpPasswordEncoder.getInstance()//spring用于加密的一个算法return new BCryptPasswordEncoder();}加密方式,如果不注入会在密码比对环节出问题
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/**").anonymous()//放行接口.anyRequest().authenticated();//其它全部需要认证 // .and() // .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success// http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}加载security前会加载的过滤器 可设置校验的接口匹配 这里的.antMatchers("/**").anonymous()时放行所有 既所有接口都会进入auth校验
ps:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
.anyRequest().permitAll()//其它请求全部放行
.and()
.formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success
}
2、设计登录接口
改写两部分 一部分是UserDetailsService(默认是从内存传用户名密码到UserDetails对象)
改成:自己设计UserDetailsService的实现类从数据库加载
二部分是,重写的登录接口,不用默认的(返回JWT令牌,并将token存入redis缓存)
ps:token的claim最好用普通字段 不然可能会出现实体类解析失败 这里存userId(因为redis序列化会破坏实体类、序列化最好是List<简单类型>)
config配置:
package com.example.config;import com.example.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.EnableWebSecurity;
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;/*** @author Mr.M* @version 1.0* @description 安全管理配置* @date 2022/9/26 20:53*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) //开启资源授权配置 权限和资源是两码事
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {// @Autowired JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//配置用户信息服务
// @Bean
// public UserDetailsService userDetailsService() {
// //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
// manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
// return manager;
// }
//@AutowiredJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//引入Bean AuthenticationManager 供调用@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic PasswordEncoder passwordEncoder() {
// //密码为明文方式
// return NoOpPasswordEncoder.getInstance()//spring用于加密的一个算法return new BCryptPasswordEncoder();}//配置安全拦截机制//__________________这里的所有路径都是不包含上下文路径的@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/login").anonymous()//放行接口 未登录才能访问.antMatchers().permitAll()//.anonymous()未登录 已登录不可访问//.authenticated()已登录//.permitAll()未登录已登录都可访问//接口权限配置.antMatchers("/getUser/*").hasAnyAuthority("getUser2").anyRequest().authenticated();//其它全部需要认证
// .and()
// .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success//认证过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//security允许跨域http.cors();}}
ps:配置有过滤对象,授权权限、跨域(spring跨域+security跨域)
spring跨域配置
package com.example.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.FormContentFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author Mr.Lan* @version 1.0* @ClassName CrosConfig$* @description Springboot跨域配置 等同nacos配置(如网关) 但也需要SpringSecurity配置* @date 2024/5/19 0:32**/
@Configuration
public class CrosConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedHeaders("Content-Type","X-Requested-With","accept,Origin","Access-Control-Request-Method","Access-Control-Request-Headers","token").allowedMethods("*").allowedOrigins("*").allowCredentials(true);}/*** 支持PUT、DELETE请求*/@Beanpublic FormContentFilter httpPutFormContentFilter() {return new FormContentFilter();}}
1)UserDetailsService实现类重写:
package com.example.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.entity.LoginUser;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.checkerframework.checker.units.qual.A;
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.Service;
import org.springframework.util.ObjectUtils;import java.sql.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;/*** @author Mr.Lan* @version 1.0* @ClassName UserDetailsServiceImpl$* @description TODO* @date 2024/5/17 15:52**/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@AutowiredUserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//传参是用户姓名//数据库查询用户信息以及权限信息LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>();userLambdaQueryWrapper.eq(User::getUsername,s);User user = userMapper.selectOne(userLambdaQueryWrapper);if( ObjectUtils.isEmpty(user)){throw new RuntimeException("用户不存在");}//查询权限信息ArrayList<String> permissions = new ArrayList<>(Arrays.asList("getUser", "getUser1"));LoginUser loginUser = new LoginUser(user,permissions);//返回UserDeatil对象//返回接口的实现类相当于返回了接口return loginUser;//返回后后面会校验密码}
}
ps:对于封装成UserDetails接口返回:可以返回实现类(接口的实例)
两种情况:
第一种 直接new实现类 然后将自己的参数传进去封装起来
第二种 创建实现类 将自己的参数与实现类联系起来
这里是第二种:
package com.example.entity;import com.alibaba.fastjson.annotation.JSONField;
import com.example.entity.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*** @author Mr.Lan* @version 1.0* @ClassName LoginUser$* @description LoginUser充当中间人实现UserDetails与User的关系* @date 2024/5/17 16:10**/
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;private ArrayList<String> permisssions;@JSONField(serialize = false)private List<SimpleGrantedAuthority> permisssionsList;public User getUser() {return user;}public LoginUser(User user, ArrayList<String> permisssions) {this.user = user;this.permisssions = permisssions;}public LoginUser(User user) {this.user = user;}public void setUser(User user) {this.user = user;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {
// List<GrantedAuthority> collect = new ArrayList<>();
// for (String permisssion : this.permisssions) {
// SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permisssion);
// collect.add(simpleGrantedAuthority);
// }
// List<GrantedAuthority> collect = this.permisssions.stream().map(permisssion -> {
// return new SimpleGrantedAuthority(permisssion);
// }).collect(Collectors.toList());if (permisssionsList != null) {
// return this.permisssionsList;//加this表示当前对象的属性 不加表示这个类中字段的值return permisssionsList;}this.permisssionsList = this.permisssions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return permisssionsList;}@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;}
}
2)书写登录实现类(调用authenticationManager、可以与后面的逻辑连贯起来)
package com.example.service.impl;import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.MapUtils;
import com.alibaba.spring.util.ObjectUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.LoginUser;
import com.example.entity.Result;
import com.example.entity.User;
import com.example.service.UserService;
import com.example.mapper.UserMapper;
import com.example.utils.RedisTemplateUtils;
import io.jsonwebtoken.Claims;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapProperties;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;import javax.xml.crypto.dsig.keyinfo.RetrievalMethod;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;import static com.example.utils.JwtUtils.generateJwt;/**
* @author Admin
* @description 针对表【user】的数据库操作Service实现
* @createDate 2024-05-16 21:03:01
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>implements UserService{@AutowiredAuthenticationManager authenticationManager;@AutowiredUserMapper userMapper;@AutowiredRedisTemplateUtils redisTemplateUtils;//authenticationManager在login的方法(这里是实现类)调用,就继续传递@Overridepublic Result login(User user) {//当参数是接口时可以传接口的实现类 创建实现类封装传递 Authentication//new UsernamePasswordAuthenticationToken()的两个参数 Object 后面要用UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());//这里将返回的时认证后的结果Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);if(Objects.isNull(authenticate)){throw new RuntimeException("认证失败");}BeanUtils.copyProperties(authenticate.getPrincipal(),user);//获取返回中的信息 加密返回tokenHashMap<String, Object> claims = new HashMap<>();LoginUser principal = (LoginUser)authenticate.getPrincipal();claims.put("userId",principal.getUser().getId());String token = generateJwt(claims);//将token存入redis 并以userId为keyredisTemplateUtils.set("login:"+principal.getUser().getId(),principal,432000L);HashMap<String, String> map = new HashMap<>();map.put("token",token);
// String jsonString = JSON.toJSONString(map);return Result.success(400,"登陆成功",map);}@Overridepublic Result loginOut() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser principal = (LoginUser)authentication.getPrincipal();String userId = principal.getUser().getId();//删除redis中的tokentry {redisTemplateUtils.del("login:"+userId);} catch (Exception e) {e.printStackTrace();return Result.error("退出登录失败");}return Result.success("成功退出登录");}
}
登录接口:
package com.example.web;import com.example.entity.Result;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import com.example.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.omg.PortableInterceptor.USER_EXCEPTION;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Map;import static com.example.utils.JwtUtils.generateJwt;/*** @author Mr.Lan* @version 1.0* @ClassName LoginController$* @description TODO* @date 2024/5/16 21:05**/
@RestController
@Slf4j
public class LoginController {@AutowiredUserService userService;@GetMapping("/getUser/{id}")
// @PreAuthorize("hasAuthority('getUser2')")//需要权限才能访问User getUser(@PathVariable Long id){User user = userService.getById(id);log.info("User:{}",user);return user;}@RequestMapping("/login-success")public String loginSuccess() {return "登录成功";}
// @GetMapping("/login-success")
// public String loginSuccess() {
//
// return "登录成功";
// }@RequestMapping("/generateJwt")public String Jwt() {HashMap<String,Object> claim=new HashMap<String,Object>();claim.put("name","admin");claim.put("password","admin");String token = generateJwt(claim);return token;}@PostMapping("/login")public Result login(@RequestBody User user) {return userService.login(user);}@GetMapping("/loginOut")public Result loginOut() {return userService.loginOut();}}
四、认证加授权
在配置类中将该过滤器置于UsernamePasswordAuthenticationFilter过滤器之前
package com.example.filter;import com.example.entity.LoginUser;
import com.example.utils.JwtUtils;
import com.example.utils.RedisTemplateUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.rmi.server.ExportException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;import static com.example.utils.JwtUtils.parseJWT;/*** @author Mr.Lan* @version 1.0* @ClassName JwtAuthenticationTokenFilter$* @description token中存用户信息 方便获取 提高查询效率 完成授权 而tJWT就是通过userId校验* @date 2024/5/17 21:27**/
@Slf4j
@Component
//@WebFilter(urlPatterns = "/*")
public class JwtAuthenticationTokenFilter implements Filter {@AutowiredRedisTemplateUtils redisTemplateUtils;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("初始化");Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest Request, ServletResponse Response, FilterChain filterChain) throws ServletException, IOException {HttpServletRequest servletRequest = (HttpServletRequest) Request;HttpServletResponse servletResponse = (HttpServletResponse) Response;//获取请求头的tokenString token = servletRequest.getHeader("token");if(StringUtils.isEmpty(token)){//未登录放行filterChain.doFilter(Request,Response);}Map<String, Object> claims=null;try {claims = parseJWT(token);} catch (Exception e) {throw new RuntimeException("token异常");}String userId = claims.get("userId").toString();String key="login:"+userId;//获取缓存的key看是否过期关键快速是获取用户的一些信息//一般认证和授权分开的 授权的token从redis获取LoginUser loginUser = (LoginUser)redisTemplateUtils.get(key);if(Objects.isNull(loginUser)){throw new RuntimeException("token非法");}//authorities存认证信息//todo LoginUser保护用户信息和权限信息UsernamePasswordAuthenticationToken info = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());SecurityContextHolder.getContext().setAuthentication(info);//已登录放行filterChain.doFilter(Request,Response);}@Overridepublic void destroy() {log.info("结束");Filter.super.destroy();}
}
ps:在封装SecurityContextHolder中加入权限信息,后面的FilterSecurityInterceptor会根据接口所需权限进行拦截
权限可用注解或者配置类中配置
五、工具类:(可网上随便找了用)
jwt
package com.example.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;
import java.util.Map;/*** @author Mr.Lan* @version 1.0* @ClassName JwtUUtils$* @description TODO* @date 2024/5/17 12:30**/
public class JwtUtils {private static String signKey = "lanjie";//签名密钥private static Long expire = 43200000L; //有效时间/*** 生成JWT令牌* @param claims JWT第二部分负载 payload 中存储的内容* @return*/public static String generateJwt(Map<String, Object> claims){String jwt = Jwts.builder().addClaims(claims)//自定义信息(有效载荷).signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部).setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间.compact();return jwt;}/*** 解析JWT令牌* @param jwt JWT令牌* @return JWT第二部分负载 payload 中存储的内容*/public static Claims parseJWT(String jwt){Claims claims = Jwts.parser().setSigningKey(signKey)//指定签名密钥.parseClaimsJws(jwt)//指定令牌Token.getBody();return claims;}
}
redis
package com.example.utils;import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import lombok.extern.slf4j.Slf4j;import javax.annotation.Resource;/*** @author 陈振东* 基于spring和redis的redisTemplate工具类 针对所有的hash 都是以h开头的方法 针对所有的Set 都是以s开头的方法 不含通用方法 针对所有的List 都是以l开头的方法*/
@Component
@Slf4j
public class RedisTemplateUtils {@Resourceprivate RedisTemplate<String, Object> redisTemplate;// =============================1-common============================/*** 指定缓存失效时间** @param key 键* @param time 时间(秒)* @return*/public boolean expire(String key, long time) {try {if (time > 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 根据key 获取过期时间** @param key 键 不能为null* @return 时间(秒) 返回0代表为永久有效*/public long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在** @param key 键* @return true 存在 false不存在*/public boolean hasKey(String key) {try {return redisTemplate.hasKey(key);} catch (Exception e) {log.error(key, e);return false;}}/*** 删除缓存** @param key 可以传一个值 或多个*/@SuppressWarnings("unchecked")public void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);} else {redisTemplate.delete(CollectionUtils.arrayToList(key));}}}// ============================2-String=============================/*** 普通缓存获取** @param key 键* @return 值*/public Object get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入** @param key 键* @param value 值* @return true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 普通缓存放入并设置时间** @param key 键* @param value 值* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key, Object value, long time) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {log.error(key, e);return false;}}// /**
// * 递增 适用场景: https://blog.csdn.net/y_y_y_k_k_k_k/article/details/79218254 高并发生成订单号,秒杀类的业务逻辑等。。
// *
// * @param key 键
// * @param 要增加几(大于0)
// * @return
// */public long incr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递增因子必须大于0");}return redisTemplate.opsForValue().increment(key, delta);}// /**
// * 递减
// *
// * @param key 键
// * @param by 要减少几(小于0)
// * @return
// */public long decr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}// ================================3-Map=================================/*** HashGet** @param key 键 不能为null* @param item 项 不能为null* @return 值*/public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);}/*** 获取hashKey对应的所有键值** @param key 键* @return 对应的多个键值*/public Map<Object, Object> hmget(String key) {return redisTemplate.opsForHash().entries(key);}/*** HashSet** @param key 键* @param map 对应多个键值* @return true 成功 false 失败*/public boolean hmset(String key, Map<String, Object> map) {try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {log.error(key, e);return false;}}/*** HashSet 并设置时间** @param key 键* @param map 对应多个键值* @param time 时间(秒)* @return true成功 false失败*/public boolean hmset(String key, Map<String, Object> map, long time) {try {redisTemplate.opsForHash().putAll(key, map);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key 键* @param item 项* @param value 值* @return true 成功 false失败*/public boolean hset(String key, String item, Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key 键* @param item 项* @param value 值* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间* @return true 成功 false失败*/public boolean hset(String key, String item, Object value, long time) {try {redisTemplate.opsForHash().put(key, item, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 删除hash表中的值** @param key 键 不能为null* @param item 项 可以使多个 不能为null*/public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);}/*** 判断hash表中是否有该项的值** @param key 键 不能为null* @param item 项 不能为null* @return true 存在 false不存在*/public boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);}/*** hash递增 如果不存在,就会创建一个 并把新增后的值返回** @param key 键* @param item 项* @param by 要增加几(大于0)* @return*/public double hincr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, by);}/*** hash递减** @param key 键* @param item 项* @param by 要减少记(小于0)* @return*/public double hdecr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, -by);}// ============================4-set=============================/*** 根据key获取Set中的所有值** @param key 键* @return*/public Set<Object> sGet(String key) {try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {log.error(key, e);return null;}}/*** 根据value从一个set中查询,是否存在** @param key 键* @param value 值* @return true 存在 false不存在*/public boolean sHasKey(String key, Object value) {try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {log.error(key, e);return false;}}/*** 将数据放入set缓存** @param key 键* @param values 值 可以是多个* @return 成功个数*/public long sSet(String key, Object... values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {log.error(key, e);return 0;}}/*** 将set数据放入缓存** @param key 键* @param time 时间(秒)* @param values 值 可以是多个* @return 成功个数*/public long sSetAndTime(String key, long time, Object... values) {try {Long count = redisTemplate.opsForSet().add(key, values);if (time > 0)expire(key, time);return count;} catch (Exception e) {log.error(key, e);return 0;}}/*** 获取set缓存的长度** @param key 键* @return*/public long sGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {log.error(key, e);return 0;}}/*** 移除值为value的** @param key 键* @param values 值 可以是多个* @return 移除的个数*/public long setRemove(String key, Object... values) {try {Long count = redisTemplate.opsForSet().remove(key, values);return count;} catch (Exception e) {log.error(key, e);return 0;}}// ============================5-zset=============================/*** 根据key获取Set中的所有值** @param key 键* @return*/public Set<Object> zSGet(String key) {try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {log.error(key, e);return null;}}/*** 根据value从一个set中查询,是否存在** @param key 键* @param value 值* @return true 存在 false不存在*/public boolean zSHasKey(String key, Object value) {try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {log.error(key, e);return false;}}public Boolean zSSet(String key, Object value, double score) {try {return redisTemplate.opsForZSet().add(key, value, 2);} catch (Exception e) {log.error(key, e);return false;}}/*** 将set数据放入缓存** @param key 键* @param time 时间(秒)* @param values 值 可以是多个* @return 成功个数*/public long zSSetAndTime(String key, long time, Object... values) {try {Long count = redisTemplate.opsForSet().add(key, values);if (time > 0)expire(key, time);return count;} catch (Exception e) {log.error(key, e);return 0;}}/*** 获取set缓存的长度** @param key 键* @return*/public long zSGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {log.error(key, e);return 0;}}/*** 移除值为value的** @param key 键* @param values 值 可以是多个* @return 移除的个数*/public long zSetRemove(String key, Object... values) {try {Long count = redisTemplate.opsForSet().remove(key, values);return count;} catch (Exception e) {log.error(key, e);return 0;}}// ===============================6-list=================================/*** 获取list缓存的内容** @param key 键* @param start 开始 0 是第一个元素* @param end 结束 -1代表所有值* @return* @取出来的元素 总数 end-start+1*/public List<Object> lGet(String key, long start, long end) {try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {log.error(key, e);return null;}}/*** 获取list缓存的长度** @param key 键* @return*/public long lGetListSize(String key) {try {return redisTemplate.opsForList().size(key);} catch (Exception e) {log.error(key, e);return 0;}}/*** 通过索引 获取list中的值** @param key 键* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推* @return*/public Object lGetIndex(String key, long index) {try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {log.error(key, e);return null;}}
//
// /**
// * 将list放入缓存
// *
// * @param key 键
// * @param value 值
// * @param time 时间(秒)
// * @return
// */public boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 将list放入缓存** @param key 键* @param value 值* @param time 时间(秒)* @return*/public boolean lSet(String key, Object value, long time) {try {redisTemplate.opsForList().rightPush(key, value);if (time > 0)expire(key, time);return true;} catch (Exception e) {log.error(key, e);return false;}}// /**
// * 将list放入缓存
// *
// * @param key 键
// * @param value 值
// * @param time 时间(秒)
// * @return
// */public boolean lSet(String key, List<Object> value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 将list放入缓存** @param key 键* @param value 值* @param time 时间(秒)* @return*/public boolean lSet(String key, List<Object> value, long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0)expire(key, time);return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 根据索引修改list中的某条数据** @param key 键* @param index 索引* @param value 值* @return*/public boolean lUpdateIndex(String key, long index, Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {log.error(key, e);return false;}}/*** 移除N个值为value** @param key 键* @param count 移除多少个* @param value 值* @return 移除的个数*/public long lRemove(String key, long count, Object value) {try {Long remove = redisTemplate.opsForList().remove(key, count, value);return remove;} catch (Exception e) {log.error(key, e);return 0;}}}
redis序列化:
package com.example.config;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.util.IOUtils;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;/*** {@link RedisSerializer} FastJson Generic Impl* @author lihengming* @since 1.2.36*/
public class RedisSerialization implements RedisSerializer<Object> {private final static ParserConfig defaultRedisConfig = new ParserConfig();static { defaultRedisConfig.setAutoTypeSupport(true);}public byte[] serialize(Object object) throws SerializationException {if (object == null) {return new byte[0];}try {return JSON.toJSONBytes(object, SerializerFeature.WriteClassName);} catch (Exception ex) {throw new SerializationException("Could not serialize: " + ex.getMessage(), ex);}}public Object deserialize(byte[] bytes) throws SerializationException {if (bytes == null || bytes.length == 0) {return null;}try {return JSON.parseObject(new String(bytes, IOUtils.UTF8), Object.class, defaultRedisConfig);} catch (Exception ex) {throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);}}
}
六、逻辑梳理
这里用的springSecurity实现了用户的登录、认证、授权
1、登录
自定义登录接口实现了第一层的过滤器,包括了密码校验,以及拥有的权限
登录是通过改写过滤器,
利用authenticationManager.authenticate(usernamePasswordAuthenticationToken);
引入authenticate串联起来整个secrity登录过程
2、认证
认证接口实现,首先认证是通过新增过滤器(同时加权限)。对token进行校验(是否有效以及是否过期等)并设置security上下文
下面介绍两个变量
1)security的上下文 SecurityContextHolder(存放用户的账户、密码、权限信息)
存: UsernamePasswordAuthenticationToken info = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(info);1、存入两个参数(一般用户名密码)
2、存三个(前面两个自定义,后面的是授权信息)
取:
通过Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
其中SecurityContextHolder 是 Spring Security 框架提供的一个重要的类,用于保存和获取当前请求的安全上下文信息。
2)密码校验 UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
这里存入输入的用户名和密码
这里输入的是数据库用户 但是在loginUser中重写了后去用户名密码是从User中获取 因此相当于传入了用户名密码,后面会在图2的第七步校验用户名密码。
ps:oauth2协议可以更加方便的实现认证授权以及分布式认证授权。