SpringSecurity+Token实现权限校验
1.Spring Security简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
1.1.需求
实现权限校验,指定用户可以访问指定页面
1.2代码解读分析
1).继承WebSecurityConfigurerAdapter
类
2).重写 configure
方法
protected void configure(HttpSecurity httpSecurity)
注:此方法用来添加校验规则
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/*.ttf",
"/*.woff",
"/*.gif",
"/*.eot",
"/*.json",
"/*.woff2",
"/*.png",
"/*.ico",
"/*.svg",
"/*.jpg",
"/*.html",
"/**/*.ttf",
"/**/*.woff",
"/**/*.gif",
"/**/*.eot",
"/**/*.json",
"/**/*.woff2",
"/**/*.png",
"/**/*.ico",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll().
antMatchers(
//mybatis复习相关的接口全部放行,同学们可以通过postMan进行测试而不需要进行权限认证
"/review/**",
"/review"
).permitAll()
.antMatchers("/common/downloadByMinio**").permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
}
上面有个jwt的过滤器
里面继承了一个类
如果请求中有token则放行
2).添加权限
那么 它是怎么添加权限的那?我们点进参数里看看...
可以看到里面是个接口,返回对象是个
咦。。。这个是不是放用户名和密码 和权限的那,那么看看它的实现类是怎么写的吧
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
return createLoginUser(user);
}
可以看到首先根据用户名去查对象,并做了一系列判断,最后通过之后调用了这个方法
这个完成对象的初始化,那么这个调用的service又是干什么的那?
这个是把该用户所对应的全部权限,从数据库中查询出来存入集合中,完成对象初始化的
我们再回到之前的配置类
至此,该用户的所拥有的全部权限就获取到了,完成了每个用户所看到的界面不一样的情况,但是这个还要结合一个注解来使用
@PreAuthorize("@ss.hasPermi('clues:course:list')")
这个注解为 spring security 的原生注解 里面返回布尔类型数据,为true就走这个方法
点进去有这么个方法,是不是很熟悉,集合参数里放的就是刚才该用户的权限列表集合,如果包含传进来的字符串,则返回true
让其访问,至此功能就实现了
2.SpringBoot中JWT是如何使用的
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。中间用点(.)分隔成三个部分。注意JWT 内部是没有换行的。
2.1.优点
基于token的验证方式它有什么优点吗?
支持跨域访问,Cookie是不允许跨域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
无状态:Token机制在服务端不需要存储session信息,因为Token自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
解耦 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
适用性更广:只要是支持http协议的客户端,就可以使用token认证。
服务端只需要验证token的安全,不必再去获取登录用户信息,因为用户的登录信息已经在token信息中。
基于标准化:你的API可以采用标准化的 JSON Web Token (JWT).
2.1.1.组成
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。中间用点(.)分隔成三个部分。注意JWT 内部是没有换行的。
2.2.使用
1).当前端传过来登录请求数据时,把对象生成Token存起来
public String createToken(LoginUser loginUser) {
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
//存放非敏感信息
claims.put("username",loginUser.getUsername());
claims.put("nickName",loginUser.getUser().getNickName());
claims.put("createTime",loginUser.getUser().getCreateTime());
return createToken(claims);
}
这里用了两个方法
第一个:
/**
* 设置用户代理信息
*
* @param loginUser 登录信息
*/
public void setUserAgent(LoginUser loginUser)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
设置代理信息
第二个:
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
刷新令牌有效期,并将对象存入Redis中
最后调用了这个方法,返回生成的令牌
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
secret
为签名,保证数据安全性
2).接下来我们看看如何取出来
可以看到我们传进去的是一个HttpServletRequest
请求,因为我们把Token放请求头里了,那么我们点进去看看,是怎么取的
这个方法总共调用了这三个方法,最终取出来了user对象,接下来一步一步看吧
第一个方法获取Token字符串
第二个方法根据Token字符串获取集合
第三个方法根据Token存入的唯一的UUID获得存入缓存中的key
最终取到user对象,至此Token存取完成。
以上为个人梳理,有不对的地方还请指正