Springboot整合Shiro: 详细的权限管理(编程式和注
该篇博客主要内容:编程式授权方式、注解式授权方式、403页面配置、前端shiro标签使用
接上一篇博客 > Springboot整合Shiro:简洁的身份认证
*******完整代码在文章最下面,转载请说明出处,谢谢 ********
Shiro的授权流程跟认证流程类似:
- 创建
SecurityManager
安全管理器 -
Subject
主体带授权信息执行授权,请求到SecurityManager
-
SecurityManager
安全管理器调用Authorizer
授权 -
Authorizer
结合主体一步步传过来的授权信息与Realm
中的数据比对,授权
(1)我们已经在ShiroConfig配置类中配置好了SecurityManager
安全管理器
/**
* 配置Shiro核心 安全管理器 SecurityManager
* SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
securityManager.setRealm(new CustomRealm());
return securityManager;
}
(2)下面对自定义Realm类CustomRealm
中的授权方法doGetAuthorizationInfo
进行重写
@Override
/**
* 授权
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取当前登录的用户
User user = (User) principal.getPrimaryPrincipal();
//通过SimpleAuthenticationInfo做授权
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//添加角色
simpleAuthorizationInfo.addRole(user.getRole());
//添加权限
simpleAuthorizationInfo.addStringPermissions(user.getPermissions());
return simpleAuthorizationInfo;
}
上面主要通过SimpleAuthorizationInfo
中的addRole
和addStringPermissions
添加当前用户拥有的角色和权限,与主体的授权信息进行比对。
(3)主体调用授权请求
主体进行授权请求有两种方式,一种是编程式,一种是注解式。
①编程式:通过Subject
的hasRole()
进行角色的校检,通过isPermitted()
进行权限的校检
@GetMapping("/dog")
public String dog(){
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("dog")){
return "dog√";
}
else {
return "dog×";
}
}
@GetMapping("/cat")
public String cat(){
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("cat")){
return "cat√";
}
else {
return "cat×";
}
}
@GetMapping("/rap")
public String rap(){
Subject subject = SecurityUtils.getSubject();
if(subject.isPermitted("rap")){
return "rap";
}else{
return "没权限你Rap个锤子啊!";
}
模拟用户数据如下:
/**
* 模拟数据库数据
* @return
*/
private List<User> getUsers(){
List<User> users = new ArrayList<>(2);
List<String> cat = new ArrayList<>(3);
cat.add("sing");
cat.add("rap");
List<String> dog = new ArrayList<>(3);
dog.add("jump");
dog.add("basketball");
users.add(new User("张小黑的猫","123qwe",true,"cat",cat));
users.add(new User("张小黑的狗","123qwe",true,"dog",dog));
return users;
}
测试结果如图:
authorization.gif
②注解式:
首先需要开启Aop注解,在ShiroConfig类中新增如下方法:
/**
* 开启aop注解支持
* 即在controller中使用 @RequiresPermissions("user/userList")
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
//设置安全管理器
attributeSourceAdvisor.setSecurityManager(securityManager);
return attributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
对于未授权的用户需要进行友好的提示,一般返回特定的403页面,在ShiroConfig类中新增如下方法实现:
/**
* 处理未授权的异常,返回自定义的错误页面(403)
* @return
*/
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
/*未授权处理页*/
properties.setProperty("UnauthorizedException", "403.html");
resolver.setExceptionMappings(properties);
return resolver;
}
解释下上面的代码properties.setProperty("UnauthorizedException","403.html");
用来对抓取到UnauthorizedException
未授权异常进行处理,重定向到403.html
页面。我们需要创建403页面403.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>未授权</title>
</head>
<body>
403,您没有访问权限
</body>
</html>
然后在Controller使用注解@RequiresRoles("xxx")
和@RequiresPermissions("xxx")
进行角色和权限的校检
@GetMapping("/sing")
@RequiresRoles("cat")
public String sing(){
return "sing";
}
@GetMapping("/jump")
@RequiresPermissions("jump")
public String jump(){
return "jump";
}
测试结果如图:
authorization2.gif
总结:一般对未授权用户需要统一返回指定403页面的,使用注解更加方便;需要做业务逻辑(比如对未授权的请求记录进行预警等),使用编程式更加方便。
(4)前端的shiro标签
通常前端页面展示需要与用户的权限对等,即只给用户看到他们权限内的内容。(比如用户"张小黑的猫",对应角色“cat”,对应权限“sing”和“rap”,那么该用户登录后只展示Cat、sing和rap的按钮)
通常解决方式有两种:
其一:登录后通过读取数据库中角色和权限,获取需要展示的菜单内容,动态的在前端渲染;
其二:所有内容都在前端写好,通过前端的shiro标签控制对应权限内容部分的渲染。
这里给大家演示shiro标签的使用。(前端的shiro标签有Jsp标签、Freemarker标签、Thymeleaf标签等,演示用的为thymeleaf标签)
Shiro标签说明:
guest标签:`<shiro:guest></shiro:guest>`,用户没有身份验证时显示相应信息,即游客访问信息。
user标签:`<shiro:user></shiro:user>`,用户已经身份验证/记住我登录后显示相应的信息。
authenticated标签:`<shiro:authenticated></shiro:authenticated>`,用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。
notAuthenticated标签:`<shiro:notAuthenticated></shiro:notAuthenticated>`,用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。
principal标签:`<shiro: principal/><shiro:principal property="username"/>`,相当`((User)Subject.getPrincipals()).getUsername()`。
lacksPermission标签:`<shiro:lacksPermission name="org:create"></shiro:lacksPermission>`,如果当前Subject没有权限将显示body体内容。
hasRole标签:`<shiro:hasRole name="admin"></shiro:hasRole>`,如果当前Subject有角色将显示body体内容。
hasAnyRoles标签:`<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>`,如果当前Subject有任意一个角色(或的关系)将显示body体内容。
lacksRole标签:`<shiro:lacksRole name="abc"></shiro:lacksRole>`,如果当前Subject没有角色将显示body体内容。
hasPermission标签:`<shiro:hasPermission name="user:create"></shiro:hasPermission>`,如果当前Subject有权限将显示body体内容
使用:
①pom.xml引入相应的依赖
<!-- 兼容于thymeleaf的shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
注意:这里引用的版本是2.0.0,之前1.0.2有兼容问题
②ShiroConfig中加入配置
/**
* 用于thymeleaf模板使用shiro标签
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
③前端页面使用shiro标签
<!--使用标签后 -->
<shiro:hasRole name="dog"><a href="/dog">Dog</a></shiro:hasRole>
<shiro:hasRole name="cat"><a href="/cat">Cat</a></shiro:hasRole>
<hr>
<shiro:hasPermission name="sing"><a href="/sing">Sing</a></shiro:hasPermission>
<shiro:hasPermission name="jump"><a href="/jump">Jump</a></shiro:hasPermission>
<shiro:hasPermission name="rap"><a href="/rap">Rap</a></shiro:hasPermission>
<shiro:hasPermission name="basketball"><a href="/basketball">Basketball</a></shiro:hasPermission>
注意:使用前现在html标签内引入shiro标签,即<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
④测试结果展示:
----------------------------------------------到这里,shiro授权基本搞定了---------------------------------------------------------
下面附上项目结构和代码:
image.png
AuthorizationController.java:
/**
* @Description
* @Author 张小黑的猫
* @data 2019-05-23 16:26
*/
@RestController
public class AuthorizationController {
@GetMapping("/dog")
public String dog(){
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("dog")){
return "dog√";
}
else {
return "dog×";
}
}
@GetMapping("/cat")
public String cat(){
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("cat")){
return "cat√";
}
else {
return "cat×";
}
}
@GetMapping("/sing")
@RequiresRoles("cat")
public String sing(){
return "sing";
}
@GetMapping("/jump")
@RequiresPermissions("jump")
public String jump(){
return "jump";
}
@GetMapping("/rap")
public String rap(){
Subject subject = SecurityUtils.getSubject();
if(subject.isPermitted("rap")){
return "rap";
}else{
return "没权限你Rap个锤子啊!";
}
}
@GetMapping("/basketball")
public String basketball(){
Subject subject = SecurityUtils.getSubject();
if(subject.isPermitted("basketball")){
return "basketball";
}else{
return "你会打个粑粑球!";
}
}
}
CustomRealm.java:
/**
* @Description: shiro的自定义realm
* Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
* @Author 张小黑的猫
* @data 2019-05-22 17:51
*/
public class CustomRealm extends AuthorizingRealm {
@Override
/**
* 认证
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取用户输入的账号
String username = (String)token.getPrincipal();
//2.通过username从数据库中查找到user实体
User user = getUserByUserName(username);
if(user == null){
return null;
}
//3.通过SimpleAuthenticationInfo做身份处理
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(user,user.getPassword(),getName());
//4.用户账号状态验证等其他业务操作
if(!user.getAvailable()){
throw new AuthenticationException("该账号已经被禁用");
}
//5.返回身份处理对象
return simpleAuthenticationInfo;
}
@Override
/**
* 授权
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取当前登录的用户
User user = (User) principal.getPrimaryPrincipal();
//通过SimpleAuthenticationInfo做授权
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//添加角色
simpleAuthorizationInfo.addRole(user.getRole());
//添加权限
simpleAuthorizationInfo.addStringPermissions(user.getPermissions());
return simpleAuthorizationInfo;
}
/**
* 模拟通过username从数据库中查找到user实体
* @param username
* @return
*/
private User getUserByUserName(String username){
List<User> users = getUsers();
for(User user : users){
if(user.getUsername().equals(username)){
return user;
}
}
return null;
}
/**
* 模拟数据库数据
* @return
*/
private List<User> getUsers(){
List<User> users = new ArrayList<>(2);
List<String> cat = new ArrayList<>(2);
cat.add("sing");
cat.add("rap");
List<String> dog = new ArrayList<>(2);
dog.add("jump");
dog.add("basketball");
users.add(new User("张小黑的猫","123qwe",true,"cat",cat));
users.add(new User("张小黑的狗","123qwe",true,"dog",dog));
return users;
}
}
ShiroConfig.java:
/**
* @Description springboot中的Shiro配置类
* @Author 张小黑的猫
* @data 2019-05-22 17:17
*/
@Configuration
public class ShiroConfig {
/**
* 配置Shiro核心 安全管理器 SecurityManager
* SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
securityManager.setRealm(new CustomRealm());
return securityManager;
}
/**
* 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
* @return
*/
@Bean
public ShiroFilterFactoryBean webFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
// Map<K,V> K指的是拦截的url V值的是该url是否拦截
Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
//配置退出过滤器logout,由shiro实现
filterChainMap.put("/logout","logout");
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
filterChainMap.put("/login","anon");
filterChainMap.put("/**", "authc");
//设置默认登录的URL.
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
/**
* 开启aop注解支持
* 即在controller中使用 @RequiresPermissions("user/userList")
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
//设置安全管理器
attributeSourceAdvisor.setSecurityManager(securityManager);
return attributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
/**
* 处理未授权的异常,返回自定义的错误页面(403)
* @return
*/
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
/*未授权处理页*/
properties.setProperty("UnauthorizedException", "403.html");
resolver.setExceptionMappings(properties);
return resolver;
}
/**
* 用于thymeleaf模板使用shiro标签
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
User.java:
/**
* @Description 用户
* @Author 张小黑的猫
* @data 2019-05-22 19:18
*/
public class User {
private String username;
private String password;
private Boolean available;
private String role;
private List<String> permissions;
public User(String username, String password, Boolean available, String role, List<String> permissions) {
this.username = username;
this.password = password;
this.available = available;
this.role = role;
this.permissions = permissions;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
}
success.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>success</title>
</head>
<body>
<span th:text="'欢迎你,'+${username}"></span>
<br>
<!--使用标签前 -->
<!--<a href="/dog">Dog</a>-->
<!--<a href="/cat">Cat</a>-->
<!--<hr>-->
<!--<a href="/sing">Sing</a>-->
<!--<a href="/jump">Jump</a>-->
<!--<a href="/rap">Rap</a>-->
<!--<a href="/basketball">Basketball</a>-->
<!--<hr>-->
<!--使用标签后 -->
<shiro:hasRole name="dog"><a href="/dog">Dog</a></shiro:hasRole>
<shiro:hasRole name="cat"><a href="/cat">Cat</a></shiro:hasRole>
<hr>
<shiro:hasPermission name="sing"><a href="/sing">Sing</a></shiro:hasPermission>
<shiro:hasPermission name="jump"><a href="/jump">Jump</a></shiro:hasPermission>
<shiro:hasPermission name="rap"><a href="/rap">Rap</a></shiro:hasPermission>
<shiro:hasPermission name="basketball"><a href="/basketball">Basketball</a></shiro:hasPermission>
</body>
</html>
共同学习,欢迎指正修改~ 喵喵喵❤
下一篇文章:Springboot整合Shiro: 动态权限配置