- 1.Shiro架构图
- 2.核心API
- 3.工作流程图
- 4.SpringBoot整合Shiro
- 5.Realm类的编写
- 6.如果自定义异常抛不出去(被源码里面的上层捕获了)
- 7.登录方法
- 8.设置未授权提示页面
- 9.用户访问无权限页面给与提示
- 10.Shiro的异常
- 11.未登录状态访问无权限接口给与登录提示[前后端分离项目无效]
- 12.注解方式拦截url请求
- 13.Rest无状态关闭session创建
- 14.自定义密码比较器
- 15.shiroConfig配置文件无法获得yml里面的值的问题
- 16.未登录状态返回json而不是login.jsp
- (二)授权
- (三)其它
- (四)放行Swagger
- 内置过滤器
- 注解
- 缓存机制
- RememberMe
- 解决Vue跨域问题
1.Shiro架构图
Authentication
身份认证/登录,验证用户是不是拥有相应的身份.
Authorization
授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager
会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography
加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support
Shiro的web支持的API能够轻松地帮助保护 Web 应用程序。主要就是用来对Web程序进行一个好的支持的。
Caching
缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率
Concurrency
shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去.
Testing
提供测试支持;
Run As
允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me
记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
2.核心API
Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager
安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager
Realm
Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
3.工作流程图
1、登陆操作 携带用户名密码给subject,subject调用自己的登陆方法传递用户名和密码给权限管理器,权限管理器将用户名密码传递给开发人员编写的realm的认证方法,realm根据用户名到数据库中查找是否存在该用户,若存在将认证信息存入到session中.
2、权限管理器会自动判断传递的密码与正确密码是否一致
3、访问3类资源(页面) 过滤器寻找权限管理器判断该用户是否拥有xxx权限,权限管理器从session中取出认证信息对象,返回给realm,realm判断该用户拥有什么权限,封装到授权信息中返回给权限管理器,权限管理器将判断的结果返回给过滤器
4、访问3类资源(xxx添加需要访问service)(对于过滤器来说属于2类资源),在执行方法时,会到达前置通知(esrvice方法上添加注解@RequiresPermissions(“courier:list”)),权限通知寻找权限管理器判断该用户是否拥有xxx权限,权限管理器从session中取出认证信息对象,返回给realm,realm判断该用户拥有什么权限,封装到授权信息中返回给权限管理器,权限管理器将判断的结果返回给权限通知
其实简单来说 /userAction_login —————>请求先到达权限过滤器shiroFilter,先判断是几类资源
登录属于一类资源直接放行到—————>userActon中(userAction中调用执行subject对象(使用入口是一个操作入口对象,里面有登陆方法,登出方法,获取当前对象方法)的登陆方法subject.login方法(携带着用户名,密码)
————>subject对象调用 securityManager的login方法 权限管理器不能判断用户和密码是对的需要
————>ream认证|授权器(开发人员编写,判断用户名是否存在,拥有什么权限)————>处理完后把认证信息对象返回给securityManager()如果认证信息没有问题,权限管理器会把认证信息存入session(证明认证登陆过了)
4.SpringBoot整合Shiro
1. 添加坐标依赖
2. 编写Shiro配置文件
需要ShiroConfig
需要创建三个Bean
ShiroFilterFactoryBean
需要设置安全管理器(setSecurityManager())
Shiro内置过滤器,可以实现权限相关的拦截器
常用的过滤器:
anon: 无需认证(登录)可以访问
authc: 必须认证才能访问(也可以使用通配符的方式,比如 /user/*)
user: 如果使用了rememberMe功能可以直接访问
perms: 该资源必须得到资源权限才可以访问
role: 该资源必须得到角色权限才可以访问
DefaultWebSecurityManager
需要关联Realm类
Realm类(授权认证功能)
5.Realm类的编写
需要继承AuthorizingRealm 重写两个方法
doGetAuthenticationInfo 执行认证逻辑
doGetAuthorizationInfo 执行授权逻辑
6.如果自定义异常抛不出去(被源码里面的上层捕获了)
7.登录方法
UnknownAccountException 是用户名不存在
IncorrectCredentialsException 密码错误异常
8.设置未授权提示页面
9.用户访问无权限页面给与提示
/**
*/
@ControllerAdvice
public class MyExceptionHandler {
/**
* 拦截无权限异常
*/
@ExceptionHandler(AuthorizationException.class)
@ResponseBody
public ExceptionResponseEntity AuthorizationException(Exception e) {
**return new **ExceptionResponseEntity(ShiroExceptionEnum.**_NO_PERMISSION_**.getCode(), ShiroExceptionEnum.**_NO_PERMISSION_**.getMessage());
}
}
访问无权限的效果:
{
“code”: 701,
“message”: “您无权限访问”
}
10.Shiro的异常
DisabledAccountException(禁用的帐号)、
LockedAccountException(锁定的帐号)、
UnknownAccountException(错误的帐号)、
ExcessiveAttemptsException(登录失败次数过多)、
IncorrectCredentialsException (错误的凭证)、
ExpiredCredentialsException(过期的凭证)等,
AuthorizationException 访问没权限的接口会抛这个异常, 比如说,user权限去访问admin
11.未登录状态访问无权限接口给与登录提示[前后端分离项目无效]
需要在ShiroConfig配置类的 ShiroFilterFactoryBean 的Bean内部配置shiroFilterFactoryBean.setLoginUrl(“/toLogin”);
然后在指定这个
注意得是Get请求才行, Post请求可能不行
| @GetMapping(“/toLogin”)
public String toLogin() {
System.out.println(“没有登录”);
return “没有登录,请登录”;
} |
| —- |
12.注解方式拦截url请求
- 在ShiroConfig配置类里面添加两个Bean
| /**
- 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
- 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
* - @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/** - 开启aop注解支持
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(this.securityManager());
return authorizationAttributeSourceAdvisor;
} | | —- |
然后在类上面添加@RequiresRoles(“admin”)
整个类就需要有admin才能访问,如果没有admin权限访问就会报错信息.
13.Rest无状态关闭session创建
第五需要StatelessDefaultSubjectFactory:由于我们编写的是无状态的,每人情况是会创建session对象的,那么我们需要修改createSubject关闭session的创建。
14.自定义密码比较器
那个有点麻烦, 只需要在login 方法把接收的密码提前进行加密传给Shiro即可
@GetMapping(“/login”)
public String login(String userName, String passWord) {
Subject subject = SecurityUtils.getSubject();
//2.封装用户数据(传入的密码是进行加密处理的)
UsernamePasswordToken token = new UsernamePasswordToken(userName, AESUtils.encryption(passWord));
try {
subject.login(token);<br /> } **catch **(IncorrectCredentialsException e) {
**throw new **ExceptionUtil(**"密码输入错误"**);
}
return “登录成功”;
15.shiroConfig配置文件无法获得yml里面的值的问题
解决方法:将LifecycleBeanPostProcessor的配置方法改成静态的就可以了
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
16.未登录状态返回json而不是login.jsp
在非token应用中,自定义过滤器是未登录状态下会访问login.jsp,而不是返回json格式字符串的,这样的话在前后端分离是不行的,因为前端需要根据返回code来判断是跳着到哪个页面的
https://www.yuque.com/docs/share/a920ba02-0cca-4a9e-8e08-5b863e55b902?#
(二)授权
1.注解授权
注解名 | 作用 |
---|---|
@RequiresAuthentication | 作用于类上,方法,实例上,调用时,当前的subject是必须经过了认证的 |
@RequiresGuest | 代表无需认证即可访问. |
@RequiresPermissions | 作用于类,方法,实例上,调用时需要判断subject中是否包含当前接口中的Permission(权限信息). |
@RequiresRoles | 作用于的类,方法,实例上,调用时,需要判断subject中是否包含当前接口中的Role(角色信息). |
@RequiresUser | 作用于的类,方法,实例上,调用时,需要判断subject中是否当前应用中的用户. |
@RequiresRoles({“user”, “admin”}) //同时具有admin和user权限才能查看,少一个都不能查看
//只有user 权限可以访问,或者只有admin权限也可以访问 ,注意logical = Logical.OR 一定不能忘记
@RequiresRoles(value = {“user”, “admin”}, logical = Logical.OR)
(三)其它
1.shiro练习项目
(四)放行Swagger
在 Shiro 的配置文件中找到拦截器,将Swagger接口的路径放行即可
| @Bean
public ShiroFilterFactoryBean webFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager());
//配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
// Map
Map
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
filterChainMap.put(“/login”, “anon”);
//放行Swagger2页面,需要放行这些
filterChainMap.put(“/swagger-ui.html”, “anon”);
filterChainMap.put(“/swagger/“, “anon”);
filterChainMap.put(“/webjars/“, “anon”);
filterChainMap.put(“/swagger-resources/“, “anon”);
filterChainMap.put(“/v2/“, “anon”);
filterChainMap.put(“/static/“, “anon”);
// 2019/12/16 暂时 放行所有的请求
// filterChainMap.put(“/“, “authc”);
//设置拦截请求后跳转的URL.
shiroFilterFactoryBean.setLoginUrl(“/login”);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
} |
| —- |
注意: 该配置必须放置在以下代码之前,不然不会生效:
filterChainMap.put(“/**”, “authc”);
authc :所有url都必须认证通过才可以访问;
anon :所有url都都可以匿名访问
内置过滤器
内置过滤器分为两组:
认证过滤器:
anon authcBaisc ,auchc , user
授权过滤器:
perms , roles ,ssl ,rest ,port
文字版简介:
https://www.cnblogs.com/koal/p/5152671.html
注解
参考:
https://www.cnblogs.com/hunmeng/p/11032014.html
(一)使用注解前需要开启注解支持配置
使用注解前需要开启注解支持
在ShiroConfig 的Configuration 类里面配置
| /**
下面的代码是添加注解支持 /
@Bean
@DependsOn(“lifecycleBeanPostProcessor”)
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题 // https://zhuanlan.zhihu.com/p/29161098
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
*return advisor;
} | | —- |(二)注解有处理顺序的
Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关):
RequiresRoles
RequiresPermissions
RequiresAuthentication
RequiresUser
RequiresGuest
例如:你同时声明了RequiresRoles和RequiresPermissions,那就要求拥有此角色的同时还得拥有相应的权限。
(三)@RequiresAuthentication
已经登录才能访问的接口
使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证(也就是已经登录过了)
等同于方法subject.isAuthenticated() 结果为true时。
(四)@RequiresPermissions
当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行,会抛出异常出来
与 RequiresRoles类似
作用:
当前的用户的Subject必须有指定的权限才能执行被这个注解标注的Controller方法,如果这个当前用户没有相关权限,那么这个方法不会去执行
@RequiresPermissions(“index:hello”)
@RequiresPermissions({“index:hello”,”index:world”})
//两者有一个权限就行
@RequiresPermissions(value={“index:hello”,”index:world”},logical=Logical.OR)
使用方式:
1. 需要在继承AuthorizingRealm 的类里面的doGetAuthorizationInfo 方法返回SimpleAuthorizationInfo的里面setStringPermissions()方法里面添加权限
2. 然后在指定的方法上添加注解来限制这个接口. 只有有相关的Permissions权限的用户才能访问这个接口.
(五)@RequiresRoles
当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
可以用在Controller或者方法上。可以多个roles,多个roles时默认逻辑为 AND也就是所有具备所有role才能访问。@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface RequiresRoles { String[] value();Logical logical() default Logical.AND;}• 示例//属于user角色@RequiresRoles(“user”)//必须同时属于user和admin角色@RequiresRoles({“user”,”admin”})//属于user或者admin之一;修改logical为OR 即可@RequiresRoles(value={“user”,”admin”},logical=Logical.OR) |
---|
(六)@RequiresUser
当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
@RequiresUser
验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的(subject.isRemembered()结果为true)。
(七)@RequiresGuest
使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
@RequiresGuest
验证是否是一个guest的请求,与@RequiresUser完全相反。
换言之,RequiresUser == !RequiresGuest。
此时subject.getPrincipal() 结果为null.
缓存机制
SpringBoot 加token环境下整合Ehcache,实现用户授权信息缓存到数据库里面.
案例:ZJJ_Shiro_2020/02/20_15:18:01_d275i |
---|
(一)根据token清理用户的缓存
适合token整合的环境下来使用.
下面方法需要在 AuthorizingRealm 的子类去编写这个方法,
/ 清理shiro的缓存信息 重新赋值权限(在比如:给一个角色临时添加一个权限,需要调用此方法刷新权限,否则还是没有刚赋值的权限) @param token token */ public void reloadAuthorizingCacheByToken(String token) { Subject subject = SecurityUtils.getSubject(); String realmName = subject.getPrincipals().getRealmNames().iterator().next(); //第一个参数为用户名,第二个参数为realmName,test想要操作权限的用户 SimplePrincipalCollection principals = new SimplePrincipalCollection(token, realmName); subject.runAs(principals); super**.getAuthorizationCache().remove(subject.getPrincipals()); subject.releaseRunAs(); } |
---|
(二)退出登录同时清理缓存
customRealm.reloadAuthorizingCacheByToken(token);调用的是上面标题的方法
/ 退出登录会清理ehcache 缓存里面的东西. * @return */ @ApiOperation(value = “退出登录功能”) @GetMapping(“/logout”) public SuccessVO logout(@Autowired CustomRealm customRealm, @RequestHeader String token) { try { Subject currentUser = SecurityUtils.getSubject(); currentUser.logout(); customRealm.reloadAuthorizingCacheByToken(token); //清理缓存 } catch (Exception e) { } return new** SuccessVO(); } |
---|
RememberMe
如果不使用RememberMe功能的话,当前浏览器登录完,进行操作之后,关闭浏览器再打开之后还需要重新登录才行.
解决Vue跨域问题
- 安装插件
在WebStorm 的Terminal面板里面输入命令
npm install —save axios vue-axios
开始安装插件,安装完成之后,重启项目.
2. 修改vue项目的vue.config.js
module.exports = { baseUrl: ‘./‘, assetsDir: ‘static’, productionSourceMap: false, devServer: { open: true, //浏览器自动打开页面 host: “0.0.0.0”, //如果是真机测试,就使用这个IP port: 8080, https: false, hotOnly: false, //热更新(webpack已实现了,这里false即可) proxy: { //配置跨域 ‘/api’: { target: “http://192.168.3.171:8080/“, ws:true, changOrigin:true, pathRewrite:{ ‘^/api’:’/‘ } } } } } |
---|
然后你vue项目的需要调用接口的地方
| userMessage(formName) {
var res = this;
this.$axios({
// 这里对应着是vue.config.js配置的proxy配置的路径
url: “/api/login”,
method: “post”,
**data**: {<br />
**username**: res.**ruleForm**.**username**,<br />
**password**: res.**ruleForm**.**password<br />
**}<br />
})
.then(function(response) {
console.log(response);
if (response.data.code <= 200) {
res.**$router**.push(**'/userManage'**);<br />
} **else **{<br />
_alert_(response.**data**.**message**);<br />
}<br />
})<br />
.catch(**function**(error) {<br />
res.**$message**.error(**"系统错误: " **+ error);<br />
**_console_**.log(error);<br />
});<br />
} | | —- |
·