IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringSecurity -> 正文阅读

[Java知识库]SpringSecurity

作者:token keyword

SpringSecurity

主要是 过滤器 和 拦截器

不是功能性需求

主要就是 认证授权

SpringSecurity

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

进行设置,后面会默认跳到login.html页面,这是内置的页面

2. 认证的主要流程

image-20220105180111256

image-20220105180515765

UsernamePasswordAuthenticationFilter : 负责处理我们再登陆页面填写了用户名、密码后的登陆请求。认证工作主要是它负责。 认证过程

ExceptionTranslationFilter : 处理过滤器中抛出的任何AccessDeniedException 和 AuthenticationException 异常处理过程

FilterSecurityInterceptor : 负责权限校验的过滤器 授权处理过程

认证流程

image-20220105202419443

一般来说,对于第一Filter来讲,实际处理中需要返回数据token,所以可以使用一个controller进行处理,调用ProviderManager

image-20220105221729084

image-20220105221831757

调用过程中,怎么判断 请求携带了token,使用JWT 过滤器

在认证token 过后,怎么获取用户信息,而不频繁调用数据库, 使用redis内存访问,减少硬盘负担,挺高效率

相关设置

redis 进行序列化操作,防止乱码出现

package com.pengshi.chartroom.controller;

import com.pengshi.chartroom.utils.FastJson2JsonRedisSerializer;
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
public class RedisConfig {

    @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;
    }
}
package com.pengshi.chartroom.utils;

/**
 * @description:
 * @projectName: ChartRoom
 * @see: com.pengshi.chartroom.utils
 * @author: pc
 * @createTime: 2022/1/5 22:59
 * @version: 1.0
 */
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;

/**
 * FastJson2JsonRedisSerializer
 *  Redis使用FastJson序列化
 *  by zhengkai.blog.csdn.net
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
	private ObjectMapper objectMapper = new ObjectMapper();
	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

	private Class<T> clazz;

	static {
		ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
		//如果遇到反序列化autoType is not support错误,请添加并修改一下包名到bean文件路径
		// ParserConfig.getGlobalInstance().addAccept("com.xxxxx.xxx");
	}
	public FastJson2JsonRedisSerializer(Class<T> clazz) {
		super();
		this.clazz = clazz;
	}

	public byte[] serialize(T t) throws SerializationException {
		if (t == null) {
			return new byte[0];
		}
		return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
	}

	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);
	}
	public void setObjectMapper(ObjectMapper objectMapper) {
		Assert.notNull(objectMapper, "'objectMapper' must not be null");
		this.objectMapper = objectMapper;
	}

	protected JavaType getJavaType(Class<?> clazz) {
		return TypeFactory.defaultInstance().constructType(clazz);
	}

}

前后端分离项目,一般都会统一一下响应的格式

  • @JsonInclude(JsonInclude.Include.NON_NULL)
    

    这个是jackson的 注释,对于json中值为null 不进行传输

JWT 结构

什么是 JWT – JSON WEB TOKEN - 简书 (jianshu.com)

令牌组成

  1. 标头 Header
  2. 有效载荷 Payload
  3. 签名 Signature

形式 为 xxxxxx.yyyyyy.zzzzz

  • Header 使用的签名算法 HMAC SHA256 或者 RSA,使用base64 编码组成JWT结构

    {
        "alg" : "HS256",
        "typ" : "JWT"
    }
    

    推荐使用HS256

  • Payload 有效负载,包含声明,使用Base64 编码

    {
        "sub" : "1234567890",
        "name" : "pengchuang",
        "admin" : true
    }
    

    但是这一部分的信息,不建议放重要信息,这块信息是对称加密,截获可能被获取信息

  • Signature 就是签名 基于前两个的base64的编码,加上随机盐,也就是签名密钥,就是查看前两个信息有没有被改过,通过第三部分的签名进行验证

实现

首先将相关代码打出,其中redis的工具类和jwt的工具类,网上一大把,同时前后端交互的pojo类吧,其实就是显示状态码和系统信息的,作为前后端交互的类,也要打出。

现在从数据访问入手,对于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 '0' comment '邮箱',
    `phonenumber` varchar(11) default NULL comment '头像',
    `sex` char(1) not null default '1' 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 CHAR SET = utf8mb4 comment '用户表'

对于实现第四个步骤,调取数据库的身份信息进行验证的过程中,需要将用户信息打包成UserDetails

@TableName(value = "sys_user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable, UserDetails {
	@TableId
	private Long id;
	@TableField(value = "user_name")
	private String name;
	private String nick_name;
	@TableField(value = "password")
	private String pwd;
	private String status;
	private String email;
	private String phonenumber;
	private String sex;
	private String avatar;
	private String user_type;
	private String create_by;
	private String create_time;
	private String update_by;
	private String update_time;
	private Integer del_flag;

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return null;
	}

	@Override
	public String getPassword() {
		return null;
	}

	@Override
	public String getUsername() {
		return null;
	}
	
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}
	// 查看是否上锁
	@Override
	public boolean isAccountNonLocked() {
		return true;
	}
	// 查看是否超时
	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}
	// 查看是否可用
	@Override
	public boolean isEnabled() {
		return true;
	}
}

直接将user类实现UserDetails的功能

由于springsecurity的安全性,密码都会经过passwordencoding进行加密解密,而对于明文存储的密码,使用{noop}表示明文的存储

对于认证过程中数据库调用的security实现类如下

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	private UserMapper userMapper;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

		// 查询用户信息
		LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(User::getName, username);
		User user = userMapper.selectOne(queryWrapper);
		// 判断是否具有这个用户
		if (Objects.isNull(user)) {
			throw new RuntimeException("用户名或密码错误!!!");
		}
		// TODO 查询对应的权限信息


		return user;
	}
}
  • 密码加密的存储,实际过程中,密码明文不会存储在数据库中的,所以我们需要加上一个密码校验其PasswordEncoder,一般使用的是SpringSecurity为我们提供的BCryptPasswordEncoder,这个类的注入要继承WebSecurityConfigureAdapter
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}
bCryptPasswordEncoder.encode(); // 加密
bCryptPasswordEncoder.matches(); // 匹配密码


BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		String str = bCryptPasswordEncoder.encode("1234");
		System.out.println(str);
		System.out.println(bCryptPasswordEncoder.matches("1234", str));

JWT 工具类

import com.pengshi.chartroom.pojo.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 对于jwt 的处理类token的设置
 * @projectName: ChartRoom
 * @see: com.pengshi.chartroom.utils
 * @author: pc
 * @createTime: 2022/1/5 23:20
 * @version: 1.0
 */
@Configuration
public class JWTUtils {
	private static final String CLAIM_KEY_USERNAME="sub";
	private static final String CLAIM_KEY_CREATED="created";

	@Value("${jwt.secret}")
	private String secret = "good";
	@Value("${jwt.expiration}")
	private Long expiration = 1000L;

	/**
	 * description 根据荷载生成token
	 * @param claims
	 * @return String
	 * @author pc
	 * @createTime 2021/12/12
	 **/
	private String generateToken(Map<String, Object> claims) {
		System.out.println(secret);
		return Jwts.builder()
				.setClaims(claims)
				.setExpiration(generateExpirationDate())
				.signWith(SignatureAlgorithm.HS512, secret)
				.compact();
	}

	/**
	 * description 根据用户信息生成token
	 * @param userDetails
	 * @return String
	 * @author pc
	 * @createTime  2021/12/12
	 **/
	public String generateToken(UserDetails userDetails) {
		Map<String, Object> claims = new HashMap<>();
		claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
		claims.put(CLAIM_KEY_CREATED, new Date());
		return generateToken(claims);
	}

	/**
	 * description 从token中获取登陆用户名
	 * @param token
	 * @return username
	 * @author pc
	 * @createTime  2021/12/12
	 **/
	public String getUserNameFromToken(String token) {
		String username;
		try {
			Claims claims = getClaimsFromToken(token);
			username = claims.getSubject();
		} catch (Exception e) {
			username = null;
		}

		return username;
	}

	/**
	 * description 验证token是否有效
	 * @param token
	 * @param userDetails
	 * @return boolean
	 * @author pc
	 * @createTime 2021/12/12
	 **/
	public boolean validateToken(String token, UserDetails userDetails) {
		String username = getUserNameFromToken(token);
		return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
	}

	/**
	 * description 判断token是不是失效了,过期了
	 * @param token
	 * @return expireDate.before(new Date())
	 * @author pc
	 * @createTime 2021/12/12
	 **/
	private boolean isTokenExpired(String token) {
		Date expireDate = getExpiredDateFromToken(token);
		return expireDate.before(new Date());
	}
	/**
	 * description 判断token是不是可以刷新
	 * @param token
	 * @return  boolean
	 * @author pc
	 * @createTime 2021/12/12
	 **/
	public boolean canRefresh (String token) {
		return !isTokenExpired(token);
	}

	/**
	 * description 刷新token
	 * @param token
	 * @return String
	 * @author pc
	 * @createTime 2021/12/12
	 **/
	public String refreshToken(String token) {
		Claims claims = getClaimsFromToken(token);
		claims.put(CLAIM_KEY_CREATED, new Date());
		return generateToken(claims);
	}

	/**
	 * description 从token中获取创建时间
	 * @param  token
	 * @return Date
	 * @author pc
	 * @createTime 2021/12/12
	 **/
	private Date getExpiredDateFromToken(String token) {
		Claims claims = getClaimsFromToken(token);
		return claims.getExpiration();
	}

	/**
	 * description 从token中获取荷载
	 * @param token
	 * @return  Claims
	 * @author pc
	 * @createTime  2021/12/12
	 **/
	public Claims getClaimsFromToken(String token) {
		Claims claims = null;
		try {
			claims = Jwts.parser()
					.setSigningKey(secret)
					.parseClaimsJws(token)
					.getBody();
		} catch (Exception e) {
			System.out.println("转译有问题");
			e.printStackTrace();
		}
		return claims;
	}

	/**
	 * description 生成token失效时间
	 * @return Date
	 * @author pc
	 * @createTime 2021/12/12
	 **/
	public Date generateExpirationDate() {
		return new Date(System.currentTimeMillis() + expiration * 1000);
	}

	public static void main(String[] args) {
		User user = new User();
		user.setName("pengshi");
		JWTUtils jwtUtils = new JWTUtils();
		System.out.println(jwtUtils.generateToken(user));
	}
}

登陆接口

自定义登陆接口,其实就是Spring中的AuthenticationManager 就是上面架构图中,时序图的第二部,主要是管理页面的访问功能,加上发送认证请求给后面,其中对于访问页面的控制中,其实可以添加我们自己自定义的页面,同时放行一些页面。

在这里可以卡一个jwt认证过滤器,对于已经认证的请求,可以放行给token过去,毕竟jwt也是无状态的

对于端口的暴露,使用的是WebSecurityConfigurationAdapter中的authenticationManagerBean方法,进行对于authenticationManager的设计

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}


	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
				// 关闭crsf
				.csrf().disable()
				// 不通过Session获取SecurityContext
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
				.and()
				.authorizeRequests()
				// 对于登录接口 允许匿名访问的
				.antMatchers("/user/login").anonymous()
				// 除上面外的所有请求全部需要鉴权认证
				.anyRequest().authenticated();
	}

	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}
}

  • 注意在上面时序图中,我们可以知道前端发送的数据在第一个Filter中封装成了Authenticaion对象,但是可以使用UsernamePasswordAuthenticationToken(实现了Authenticaion的方法)

  • 放行的页面或者是接口使用Configure进行重写,放行相关链接

image-20220106130330738

这是认证对象的组成信息,其中getPrincipal就是返回的User对象,我们需要理解的是,authenticationManager,通过authenticate方法 调用到时序图的第四层进行数据库中用户信息的认证和获取,返回还是一个Authentication对象,但是这个对象的信息是对于真正的数据库信息来说的,这里面的信息就是数据库中的信息。而像下面的UsernamePasswordAuthenticationToken 发送到后面 时序图DAO层中的进行拆解,拿到账号和密码,可以看到下面的过程也是对于账号密码的封装发送到DAO层中,DAO层中拆解,拿到信息,后面DetailsService层中 UserDetailsService ,调用loadUserByUsername 加上上层DAO获得的username进行数据库信息查询,返回UserDatils信息回去,这里我将User实现UserDatils接口,将User类作为一个通用的认证数据

@Service
public class LoginServiceImpl implements LoginService {

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private RedisCache redisCache;

	@Override
	public ResponseResult login(User user) {
		// System.out.println(user);
		// AuthenticationManager authenticate 进行用户认证
		UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
				user.getName(), user.getPwd());
		Authentication authenticate = authenticationManager.authenticate(authenticationToken);


		// 认证没有通过,提示
		if (authenticate == null) {
			throw  new RuntimeException("登陆失败!!!");
		}
		// 认证通过提示,使用userid生成一个jwt 存入 ResponseResult
		User user1 = (User) authenticate.getPrincipal();
		Long id = user1.getId();
		String token = JWTUtils.generateToken(user1);
		// 把完整信息存入redis中
		redisCache.setCacheObject("login:"+user1.getId(), user1);

		Map<String, String> hashMap = new HashMap();
		hashMap.put("token", token);
		return new ResponseResult(200, "登陆成功", hashMap);
	}
}

Redis 作为用户信息的存储

使用redis是防止对于数据库中,用户调取信息的频繁访问,使用redis,因为内存可以快速访问,主还是jwt是无状态的

redis配置

@Configuration
public class RedisConfig {
	@Bean
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		// String 类型 key 序列器
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		// String 类型 value序列器
		redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
		redisTemplate.setConnectionFactory(factory);
		return redisTemplate;
	}
}

properties的配置

spring.redis.timeout=10000ms
spring.redis.host=39.108.94.255
spring.redis.port=6379
spring.redis.database=0
spring.redis.password=root
spring.redis.lettuce.pool.max-active=1024
spring.redis.lettuce.pool.max-wait=10000ms
spring.redis.lettuce.pool.max-idle=200
spring.redis.lettuce.pool.min-idle=5

JWT 过滤器

我们使用的是springmvc提供的OncePerRequestFilter的类,继承这个类,主要是通过这个类进行过滤器的设计,从英语意思上来看,每次请求至少经过一个,所以使用这个过滤器更为方便

注意可以从上面的时序图中看出过滤器Filter请求后,响应的时候还是会经过过滤器的,这个jwt过滤器放在前面

注意一下对于 UsernamePasswordAuthenticationToken 解析中

  • 可以知道,三个参数方法的认证中,是对于认证通过的标志,两个参数为false
  • 使用三个参数的方法,可以卡在token验证jwt过滤器上面,放行后面的拦截器,验证通过
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    this.setAuthenticated(false);
}

public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    super.setAuthenticated(true);
}
  • principal 其实就是认证的对象 credentials就是认证的凭证,一般来说,假如还没有验证setAuthenticated(false);在credentials添加密码,这个密码会自动和PasswordEncoding 和 后端得出的DatilsService上的Password进行两者转换验证,认证通过setAuthenticated(true);

如何添加过滤器在拦截器之前呢,也就是对于jwt过滤器而言,要在拦截器之前进行过滤认证?

  • 使用 WebSecurityConfigurerAdapter 中 addFilterBefore 功能进行过滤器拦截,可以知道我们需要时使用UsernamePasswordAuthenticationFilter,作为设置将jwtAuthenticationTokenFilter放在UsernamePasswordAuthenticationFilter之前执行,也就是用户名和密码验证之前进行token校验,如果没有token经过这个用户密码验证,有有效的token就放行,使其畅通无阻。

  • http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
  • 注意如果遇到 UnrecognizedPropertyException

    Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "enabled" 
    这种错误是因为json格式中有些属性与转化的类不一样,所以加上下面的注解进行忽略操作
    @JsonIgnoreProperties(ignoreUnknown = true)
    
  • 后面,请求操作的时候携带请求头header 里面包含token就可以进行认证成功的操作,访问相关页面

退出登录

其实就是删除 redis里面的数据和 SecurityContextHolder的数据,表明退出了

由于 /logout 这个url被spring占用默认为退出功能,所以可以在拦截器那里通过config http 关掉logout功能,使用该url, 相关配置就是下面关闭了logout功能,能够正常占用logout 连接进行退出操作

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // 关闭crsf
        .csrf().disable()
        // 不通过Session获取SecurityContext
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
        // 对于登录接口 允许匿名访问的
        .antMatchers("/login").anonymous()
        // 除上面外的所有请求全部需要鉴权认证
        .anyRequest().authenticated()
        .and()
        .logout()
        .disable();

    http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
  • antMatchers(url).permitAll() 即使登不登陆都可以访问

  • antMatchers(url).anonymous() 匿名访问,未登录下登陆

  • anyRequest().authenticated() 任意用户认证成功都可以访问

  • 注意 拦截器中 ,具有Filter部署,和session管理

2. 权限 授权

判断用户认证后是否有相关权限进行访问

开启访问权限

@EnableGlobalMethodSecurity(prePostEnabled = true)

对应使用相关注解进行判断

@RestController
public class HelloController {
   @PreAuthorize("hasAnyAuthority('test')")
   @GetMapping("/hello")
   public String hello() {
      return " fdsafdsafd";
   }
}

@PreAuthorize(“hasAnyAuthority(‘test’)”) 在访问之前判断是否具有这个权限

怎么录入权限呢?我们可以知道在UsernamePasswordAuthenticationToken后面的第三个参数是一个collection集合,其中对于DetilsService 中getAuthorities 也要进行改装。同时解释一下DetilsService 和 Authentication等实现类的区别。

  • 大致逻辑其实就是 前端发送用户密码封装为 Authentication 等实现类,传给DAO层进行验证,DAO提取其中的用户名发送给数据库连接层UserDetilsService上,进行数据库的查询,获得用户信息。

  • 由数据库获得的用户信息,被系统封装成 UserDetails 实现类,其实就是返回的查询的用户信息,再传递回给DAO层,进行密码等验证判断。

  • 实现UserDetails中的实现方法 获取权限如下

    	@Override
    	public Collection<? extends GrantedAuthority> getAuthorities() {
    		List<GrantedAuthority> list = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    		return null;
    	}
    

    其中要知道的是 权限授予必须继承了GrantedAuthority

    为了方便每次验证的时候,都不用进行转化浪费时间,可以使用全局变量保存list权限信息,在里面卡一判断,是否为空

    	@JSONField(serialize = false)
    	private List<SimpleGrantedAuthority> authorities;
    	
    	@Override
    	public Collection<? extends GrantedAuthority> getAuthorities() {
    		if (authorities != null) return authorities;
    		authorities= permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    		return authorities;
    	}
    

    这里需要注意的是权限信息,不能进行JSON流处理,要记住,不然Spring处理会报错

我们在 UserDetails loadUserByUsername 中写入权限信息,注入到 UserDetails实现类中,其中权限信息可以从数据库中获取,进行写入,同时登陆的时候一并记录在Redis中,设置UserDetails 实现类中设置一个新的List存放权限,录入这个权限后,怎么执行其中返回权限信息?使用UserDetails的实现方法进行设置,使用 getAuthorities()使得每次调用相关url时候进行权限的获取判断,而对于需要权限的url 有 @PreAuthorize("hasAnyAuthority('test')")这个注解,可以进行判断,是否通过。

  • 特别注意:对于Mybatis很智能,能够自己对应类中属性进行数据库的匹配,但是也要注意当debug 看到var6的错误的时候,应该就是类中有些属性是没有对应数据库加上注解 @TableField(exist = false)

  • Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "
    
  • 上面错误是因为权限使用的过程中权限 json解析失败了 @JSONField(serialize = false) 使用属性注解,将这个部分不序列化,但是好像没有用,还是老老实实的写一个反序列化的类,但是写了反序列化类更没什么用处,懵逼了我,反序列化类如下

    class CustomAuthorityDeserializer extends JsonDeserializer {
    	@Override
    	public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    		ObjectMapper mapper = (ObjectMapper) p.getCodec();
    		JsonNode jsonNode = mapper.readTree(p);
    		LinkedList<GrantedAuthority> grantedAuthorities = new LinkedList<>();
    		Iterator<JsonNode> elements = jsonNode.elements();
    		while (elements.hasNext()) {
    			JsonNode next = elements.next();
    			JsonNode authority = next.get("authority");
    			//将得到的值放入链表 最终返回该链表
    			grantedAuthorities.add(new SimpleGrantedAuthority(authority.asText()));
    		}
    		return grantedAuthorities;
    	}
    }
    

    最后还是使用 注解@JsonIgnore 将对应属性进行忽略

    @TableField(exist = false)
    @JsonIgnore
    private List<SimpleGrantedAuthority> authorities;
    

从数据库查询权限信息

RBRA 权限模型 (Role-Based Access Control) 即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用的权限模型

用户表-权限表-角色表

  • 多个角色可以有多个权限,使用一个关联表进行设计
  • 多个用户可以有多个角色,使用一个关联表进行设计
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-22 20:26:08  更:2022-02-22 20:29:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:03:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码