springcloud security jwt,springboot security jwt
00-1010一、登录验证流程1。春保2完整流程。Spring Security的默认登录验证过程。3.集成JWT通用流程前端响应类JWT工具类重写UserDetailsService方法重写登录接口认证过滤器退出登录授权基本流程限制对资源的访问所需权限封装权限信息RBAC权限模型自定义失败处理认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权:经过认证后判断当前用户是否有权限进行某个操作
目录
00-1010 Spring安全的原理实际上是一个过滤器链,它包含了提供各种功能的过滤器。一些核心过滤器如下:
userpasswordtauthenticationfilter:负责在登录页面填写用户名和密码后处理登录请求。
ExceptionTranslationFilter:处理筛选器链中引发的任何AccessDeniedException和AuthenticationExcption。
FilterSecurityInterceptor:负责权限验证的过滤器。
一、登录校验流程
认证接口:它的实现类,代表当前访问系统的用户,封装了用户相关的信息。
AuthenticationManager接口:定义身份验证的方法。
UserDetailsService接口:加载用户特定数据的核心接口。它定义了一种根据用户名查询用户信息的方法。
UserDetails接口:提供核心用户信息。UserDetailsService根据用户名获取并处理的用户信息封装到UserDetails对象中返回。然后将信息封装到身份验证对象中。
00-1010登录
自定义登录界面
调用ProviderManager的方法进行身份验证。如果认证通过,生成JWT。
在redis中存储用户信息
定制用户详细信息服务
查询这个实现类中的数据库。
核实
定义Jwt认证过滤器
获取令牌
解析令牌以获取用户id。
从redis获取用户信息
证券保证金
Redis使用Fastjson序列化
!-spring Data Redis Dependency-Dependency groupIdorg.springframework.boot/groupId Artifactid spring-Boot-Starter-Data-Redis/Artifactid/Dependency!- commons-pool2对象池依赖项-dependency groupid org . Apache.commons/groupid工件commons-pool 2/工件id/依赖项!- JSON工具依赖groupIdcom.alibaba/groupId ArtifactidFastJSON/ArtifactidVersion 1 . 2 . 76/version/dependencyimportcom . Alibaba . Fas
tjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.serializer.SerializerFeature;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset;import java.nio.charset.StandardCharsets; public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } private final Class<T> clazz; public FastJson2JsonRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; } /** * 序列化 */ @Override public byte[] serialize(T t) throws SerializationException { if (null == t) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } /** * 反序列化 */ @Override public T deserialize(byte[] bytes) throws SerializationException { if (null == bytes bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return (T) JSON.parseObject(str, clazz); }}
import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;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; @Configuration@AutoConfigureAfter(RedisAutoConfiguration.class)public class RedisCacheAutoConfiguration { @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用fastJson template.setValueSerializer(fastJsonRedisSerializer); // hash的value序列化方式采用fastJson template.setHashValueSerializer(fastJsonRedisSerializer); template.afterPropertiesSet(); return template; }}
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.*;import org.springframework.stereotype.Component; import java.util.*;import java.util.concurrent.TimeUnit; /** * spring redis 工具类 **/@Componentpublic class RedisUtil { @Autowired private RedisTemplate<Object, Object> redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @return 缓存的对象 */ public ValueOperations<Object, Object> setCacheObject(Object key, Object value) { ValueOperations<Object, Object> operation = redisTemplate.opsForValue(); operation.set(key, value); return operation; } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 * @return 缓存的对象 */ public ValueOperations<Object, Object> setCacheObject(Object key, Object value, Integer timeout, TimeUnit timeUnit) { ValueOperations<Object, Object> operation = redisTemplate.opsForValue(); operation.set(key, value, timeout, timeUnit); return operation; } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public Object getCacheObject(Object key) { ValueOperations<Object, Object> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public void deleteObject(Object key) { redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection */ public void deleteObject(Collection collection) { redisTemplate.delete(collection); } public Long getExpire(String key) { return redisTemplate.getExpire(key); } public void expire(String key, int expire, TimeUnit timeUnit) { redisTemplate.expire(key, expire, timeUnit); } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public ListOperations<Object, Object> setCacheList(Object key, List<Object> dataList) { ListOperations listOperation = redisTemplate.opsForList(); if (null != dataList) { int size = dataList.size(); for (Object o : dataList) { listOperation.leftPush(key, o); } } return listOperation; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public List<Object> getCacheList(String key) { List<Object> dataList = new ArrayList<>(); ListOperations<Object, Object> listOperation = redisTemplate.opsForList(); Long size = listOperation.size(key); if (null != size) { for (int i = 0; i < size; i++) { dataList.add(listOperation.index(key, i)); } } return dataList; } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public BoundSetOperations<Object, Object> setCacheSet(String key, Set<Object> dataSet) { BoundSetOperations<Object, Object> setOperation = redisTemplate.boundSetOps(key); for (Object o : dataSet) { setOperation.add(o); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public Set<Object> getCacheSet(Object key) { Set<Object> dataSet = new HashSet<>(); BoundSetOperations<Object, Object> operation = redisTemplate.boundSetOps(key); dataSet = operation.members(); return dataSet; } /** * 缓存Map * * @param key * @param dataMap * @return */ public HashOperations<Object, Object, Object> setCacheMap(Object key, Map<Object, Object> dataMap) { HashOperations hashOperations = redisTemplate.opsForHash(); if (null != dataMap) { for (Map.Entry<Object, Object> entry : dataMap.entrySet()) { hashOperations.put(key, entry.getKey(), entry.getValue()); } } return hashOperations; } /** * 获得缓存的Map * * @param key * @return */ public Map<Object, Object> getCacheMap(Object key) { Map<Object, Object> map = redisTemplate.opsForHash().entries(key); return map; } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection<Object> keys(String pattern) { return redisTemplate.keys(pattern); }}
前端响应类
@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 = "zhangao"; public static String getUUID(){ String token = UUID.randomUUID().toString().replaceAll("-", ""); return token; } /** * 生成jtw * @param subject token中要存放的数据(json格式) * @return */ public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间 return builder.compact(); } /** * 生成jtw * @param subject token中要存放的数据(json格式) * @param ttlMillis token超时时间 * @return */ public static String createJWT(String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间 return builder.compact(); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if(ttlMillis==null){ ttlMillis=JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) //唯一的ID .setSubject(subject) // 主题 可以是JSON数据 .setIssuer("sg") // 签发者 .setIssuedAt(now) // 签发时间 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥 .setExpiration(expDate); } /** * 创建token * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间 return builder.compact(); } public static void main(String[] args) throws Exception {// String jwt = createJWT("2123"); Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0"); String subject = claims.getSubject(); System.out.println(subject);// System.out.println(claims); } /** * 生成加密后的秘钥 secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 解析 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } }
创建数据库表信息和实体,配置数据库连接信息
定义mapper等一系列接口。xml等。用mybatis-plus方便一点,注意Mapper继承BaseMapper<实体类>,实体类中需要加@TableName(value = "表名") ,id字段上加 @TableId
在application.yml中配置mapperXML文件的位置
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
重写UserDetailsService的方法
创建一个类实现UserDetailsService接口,重写其中的方法。从数据库中查询用户信息,进行校验。(如果没有重写的话,就是上面说的spring security默认的使用UserDetailsService接口下面的InMemoryUserDetailsManager实现类中的方法,是在内存中查找。这个是需要根据我们具体的系统来重写的。)
@Servicepublic class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Autowired private MenuMapper menuMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询用户信息 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUserName,username); User user = userMapper.selectOne(queryWrapper); //如果没有查询到用户就抛出异常 if(Objects.isNull(user)){ throw new RuntimeException("用户名或者密码错误"); } // 查询权限 List<String> list = menuMapper.selectPermsByUserId(user.getId()); //把数据封装成UserDetails返回 return new LoginUser(user,list); }}
因为UserDetailsService方法的返回值是UserDetails类型,所以需要定义一个类,实现该接口,把用户信息封装在其中。
@Data@NoArgsConstructorpublic class LoginUser implements UserDetails { private User user; private List<String> permissions; public LoginUser(User user, List<String> permissions) { this.user = user; this.permissions = permissions; } @JSONField(serialize = false) private List<SimpleGrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { if(authorities!=null){ return authorities; } //把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象// authorities = new ArrayList<>();// for (String permission : permissions) {// SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);// authorities.add(authority);// } authorities = permissions.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return authorities; } @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; }}
重写登录接口
接下我们需要自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。
在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。
认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体
的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。
@RestControllerpublic class LoginController { @Autowired private LoginServcie loginServcie; @PostMapping("/user/login") public ResponseResult login(@RequestBody User user){ //登录 return loginServcie.login(user); } @RequestMapping("/user/logout") public ResponseResult logout(){ return loginServcie.logout(); }}
@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { //创建BCryptPasswordEncoder注入容器 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired private AuthenticationEntryPoint authenticationEntryPoint; @Autowired private AccessDeniedHandler accessDeniedHandler; @Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录接口 允许匿名访问 .antMatchers("/user/login").anonymous()// .antMatchers("/testCors").hasAuthority("system:dept:list222") // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); //添加过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); //配置异常处理器 http.exceptionHandling() //配置认证失败处理器 .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); //允许跨域 http.cors(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { //创建BCryptPasswordEncoder注入容器 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired private AuthenticationEntryPoint authenticationEntryPoint; @Autowired private AccessDeniedHandler accessDeniedHandler; @Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录接口 允许匿名访问 .antMatchers("/user/login").anonymous()// .antMatchers("/testCors").hasAuthority("system:dept:list222") // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); //添加过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); //配置异常处理器 http.exceptionHandling() //配置认证失败处理器 .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); //允许跨域 http.cors(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
@Servicepublic class LoginServiceImpl implements LoginServcie { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public ResponseResult login(User user) { //AuthenticationManager authenticate进行用户认证 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); //如果认证没通过,给出对应的提示 if(Objects.isNull(authenticate)){ throw new RuntimeException("登录失败"); } //如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userid = loginUser.getUser().getId().toString(); String jwt = JwtUtil.createJWT(userid); Map<String,String> map = new HashMap<>(); map.put("token",jwt); //把完整的用户信息存入redis userid作为key redisCache.setCacheObject("login:"+userid,loginUser); return new ResponseResult(200,"登录成功",map); } @Override public ResponseResult logout() { //获取SecurityContextHolder中的用户id UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); Long userid = loginUser.getUser().getId(); //删除redis中的值 redisCache.delet
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。