Shiro整合Springboot做权限管理:
使用shiro管理项目权限需要配置shiro的SecurityManager管理器去管理权限参数,并且需要编写CustomRealm去实现登录授权逻辑,然后在使用Shiro自带的@RequiresRoles以及@RequiresPermissions注解去管理单一接口权限,也可以在ShiroConfig中配置统一的权限管理策略。此项目使用的统一权限管理策略。
首先导入Shiro与Spring的整合包:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
此整合包可以参照https://mvnrepository.com/上的shiro-spring工具包的更新来看版本号。
导入之后,首先编写Shiro配置类,shiroConfig.java。
package com.meiszl.testupload.config;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/notLogin");
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
// 设置拦截器
Map<String,String> map = new LinkedHashMap<>();
//游客,开发权限
map.put("/guest/**","anon");
//管理员,需要角色权限 “admin”
map.put("/admin/**","roles[admin]");
//用户权限
map.put("/User/**","roles[user]");
//开放登陆接口
map.put("/login", "anon");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
System.out.println("shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 自定义身份认证 realm;
* <p>
* 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
* 否则会影响 CustomRealm类 中其他类的依赖注入
*/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
}
注意此处的方法shiroFilter(SecurityManager securityManager)中的SecurityManager securityManager参数需要导入Shiro中的SecurityManager而不是java.lang的SecurityManager。
ShiroFilterFactoryBean方法中的shiroFilterFactoryBean是Shiro导入Spring之中的Bean对象用于权限管理之用。他有许多配置设置在此仅介绍此demo所用到的一些配置,其余的高级配置可以自行查阅shiro官方手册。shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean中必须接受一个SecurityManager类去做权限管理此类在配置类的下方注入成Bean。
注:在Spring中配置Shiro需要将配置进入ShiroFilterFactoryBean的类都注入成SpringBean因为shiro在运行的时候需要使用SpringIOC容器进行权限参数的管理。
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射 shiroFilterFactoryBean.setLoginUrl("/notLogin");
此处配置的LoginUrl是指未登录状态下访问权限页面时会跳转到的Url。
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
此处配置的UnauthorizedUrl是指登陆之后访问非登录用户权限的页面所跳转的URL,即,登陆普通用户强行访问管理员页面会跳转到此URL下。
// 设置拦截器
Map<String,String> map = new LinkedHashMap<>();
此处的拦截器用于配置Shiro的管理策略,用于自动管理Role与Permission的配置来访问页面,类似于Spring的依赖注入之中包自动扫描,它会接受一些按规则编写的字符串来确定访问策略。
//游客,开发权限
map.put("/guest/**","anon");
此处的配置意思是说,在RequestMapping中配置的/guest/路径下的URL可以被无权限访问,‘/guest/**’指的是访问的URL路径,例如,localhost:8080/guest/**。而“anon”的配置意思是指无权限限制,即公共接口。
//管理员,需要角色权限 “admin”
map.put("/admin/**","roles[admin]");
此处map的Key配置与上面一样是指URL路径信息,而后面的“roles[admin]”是指必须拥有admin权限才可以访问admin路径下的接口。
//用户权限
map.put("/User/**","roles[user]");
//开放登陆接口
map.put("/login", "anon");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
此处将拦截器map配置进入shiroFilterFactoryBean。
System.out.println("shiro拦截器工厂类注入成功");
注:上述注入的SecurityManager类需要同样引入IOC容器,此处我直接定义在了ShiroConfig中并将SecurityManager的CustomRealm也定义在了shiroConfig中。
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 自定义身份认证 realm;
* <p>
* 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
* 否则会影响 CustomRealm类 中其他类的依赖注入
*/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
此处注意CustomRealm也需要注入IOC容器不能直接在SecurityManager中new实现因为你完成ShiroConfig的配置之后还需要在编写一个授权/登陆的CustomRealm来管理你整个项目的登录操作。此处只是配置shiro还并未实现整体授权/登陆框架。
接下来编写CustomRealm类去实现授权/登陆逻辑。
package com.meiszl.testupload.config;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashSet;
import java.util.Set;
public class CustomRealm extends AuthorizingRealm {
/**
* 获取授权信息
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("权限认证");
String username =(String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// //获得该用户角色
// String role = userMapper.getRole(username);
// Set<String> set = new HashSet<>();
//// 需要将 role 封装到 Set 作为 info.setRoles() 的参数
// set.add(role);
System.out.println(username);
if (username.equals("admin")) {
Set<String> set = new HashSet<>();
set.add("admin");
//设置该用户拥有的角色
info.setRoles(set);
}else{
Set<String> set = new HashSet<>();
set.add("user");
//设置该用户拥有的角色
info.setRoles(set);
}
return info;
}
/**
* 获取身份验证信息
* Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
*
* @param authenticationToken 用户身份信息 token
* @return 返回封装了用户信息的 AuthenticationInfo 实例
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("身份认证方法");
//从签名获取登录信息
// UserDomain userDomain = (UserDomain) principalCollection.getPrimaryPrincipal();
return new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),"123456",getName());
}
}
此处未从数据库中获取数据,仅仅只是做了一个示例,
doGetAuthenticationInfo方法是在需要登录验证时才会进入的方法,即当调用了Subject.login()方法才会进入(此方法在后面会在Controller中调用)而doGetAuthorizationInfo方法则是需要权限认证的时候才会调用的方法,即当你登陆了admin权限的用户之后访问admin权限的页面时会调用这个方法进行权限验证。
doGetAuthenticationInfo方法返回一个类似于验证器的东西,SimpleAuthenticationInfo()方法用于验证传入的用户信息是否合法而验证方法是与你在接口传入的信息进行比对,就是在接口或者Service中调用token(令牌) 执行认证登录。
UsernamePasswordToken token = new UsernamePasswordToken("User","123456");
//执行认证登陆
subject.login(token);
而此处的login传入令牌实际上会缓存进shiro然后再与CustomRealm中你所设置的SimpleAuthenticationInfo()方法内的用户名与密码进行比较。
注:SimpleAuthenticationInfo()方法有三个参数,第一个是用户名,但你也可以直接将用户令牌传入,shiro会自动从用户令牌中获取用户名信息,此处就是这样的用法,而第二个参数就是用户密码参数,第三个是认证标识就是记录这次认证的类似于ID一样的东西,一般使用getName()来获取。
doGetAuthorizationInfo方法用于权限认证此方法需要回传一个AuthorizationInfo授权信息一般会做判断逻辑在内部导入一个Set权限或者角色集合用于进行权限验证。
此处的CustomRealm就是最后会注入ShiroConfig的CustomRealm。
三个子测试页面的Controller配置,
package com.meiszl.testupload.Controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/admin")
public class ShiroTestadmin {
@RequestMapping("/find")
public String adtest(){
return "管理员界面";
}
}
package com.meiszl.testupload.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/guest")
public class ShiroTestGuest {
@RequestMapping("/test")
public String shiroTest(){
return "游客页面";
}
}
package com.meiszl.testupload.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/User")
public class ShiroTestUser {
@RequestMapping("/test")
public String shiroTest(){
return "用户页面";
}
}
登陆Controller配置,
package com.meiszl.testupload.Controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ShiroTestLogin {
@RequestMapping("/login")
public String ShiroLogin(@RequestParam("loginpar")String str){
if (str.equals("1")){
System.out.println("111111111111111111111");
//从SecurityUtils里边创建一个Subject
Subject subject = SecurityUtils.getSubject();
//在认证提交前准备token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
//执行认证登陆
subject.login(token);
//根据权限,指定返回数据
Session session = subject.getSession();
session.setAttribute("userName","admin");
}
if (str.equals("2")){
System.out.println("222222222222222222");
//从SecurityUtils里边创建一个Subject
Subject subject = SecurityUtils.getSubject();
//在认证提交前准备token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken("User","123456");
//执行认证登陆
subject.login(token);
//根据权限,指定返回数据
Session session = subject.getSession();
session.setAttribute("userName","admin");
}
return "登录界面";
}
@RequestMapping("/notRole")
public String ShironotRole(){
return "无权限";
}
@RequestMapping("/notLogin")
public String ShironotNotLogin(){
return "未登录";
}
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "登出成功";
}
}
登陆的时候首先需要声明一个Subject,注意此处的Subject是Shiro中的Subject,subject执行各种shiro权限操作,认证操作不需要手动触发,仅在访问URL的时候进行授权认证。Session是Shiro下的依赖包,用于存放一些用户需要记录的Session。