这篇文章主要用来介绍Shiro在SpringBoot框架中的使用,也会讲一些不同业务情景下的用法。
1 基本概念
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
3大组件:
- SecurityManager:安全管理器,提供认证和授权等框架所有功能的接口。典型的外观模式,具体实现委托给其他类。
- Subject:当前用户,不仅指人。虽然这样说,但是一般都是当做一个账户。
- Realm:安全管理器最终的委托类,认证或权限和数据的桥梁,就是实现自定义的登录就是实现这个组件中的类。
以登录举一个例子,Shiro大概的实现流程是这样的:
2 依赖引入
创建一个SpringBoot Web项目,导入Shiro包:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.7.1</version>
</dependency>
直接启动会报错:
No bean of type 'org.apache.shiro.realm.Realm' found.
这个错的原因是缺少Realm类型bean,下面我们注册了自定义的Realm就不会报错了。
3 自定义Realm实现认证和授权
按照以上步骤,我们开始写代码
3.1 实现自定义Realm
Shiro提供了非常多的Realm供我们使用,下面讲两个最重要的
- AuthenticatingRealm:认证Realm。实现doGetAuthenticationInfo()完成认证
- AuthorizingRealm:授权Realm,父类是AuthenticatingRealm,实现他的抽象方法完成认证和授权
简单来说,如果你的系统只需要登录,只用实现AuthenticatingRealm就好,如果还需要授权,那就实现AuthorizingRealm。
接下来创建MyRealm.java
public class MyRealm extends AuthorizingRealm {
/**
* 获取用户的权限
* @param pc 认证后的信息,是doGetAuthenticationInfo()返回的值
* @return 权限的信息,Shiro会将它缓存
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
return null;
}
/**
* 认证
* @param token 登录的信息
* @return 认证信息,认证成功后返回
* @throws AuthenticationException 失败抛出的异常,可自定义异常实现错误信息抛出。因为返回值中不包含错误信息,Shiro使用异常来传递认证失败的信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null;
}
}
}
因为是Demo,我就懒得建表了,直接创建一个AccountInfoData.java来辅助。
public class AccountInfoData {
/**
* 用户键值对
* class User{String loginName;String password}
*/
private static Map<String, User> users = new HashMap<>();
/**
* 用户名对应的权限
*/
private static Map<String,List<String>> permissions = new HashMap<>();
/**
* 用户名对应的角色
*/
private static Map<String,List<String>> roles = new HashMap<>();
static{
String user1 = "admin";
users.put(user1,new User(user1,"123456"));
permissions.put(user1, CollectionUtils.asList("read","write"));
roles.put(user1,CollectionUtils.asList("admin"));
}
public static User getUser(String loginName){
return users.get(loginName);
}
public static List<String> getPermission(String loginName){
return permissions.get(loginName);
}
public static List<String> getRoles(String loginName){
return roles.get(loginName);
}
}
填写认证和授权的逻辑
public class MyRealm extends AuthorizingRealm {
/**
* 获取用户的权限
* @param pc 认证后的信息,是doGetAuthenticationInfo()返回的值
* @return 权限的信息,Shiro会将它缓存
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
//获取认证的主体,也就是认证返回的信息
User user = (User) pc.getPrimaryPrincipal();
//创建一个存储权限信息的类
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加权限
if (!CollectionUtils.isEmpty(AccountInfoData.getPermission(user.getLoginName()))){
info.addStringPermissions(AccountInfoData.getPermission(user.getLoginName()));
}
//添加角色
if (!CollectionUtils.isEmpty(AccountInfoData.getRoles(user.getLoginName()))){
info.addRoles(AccountInfoData.getRoles(user.getLoginName()));
}
return info;
}
/**
* 认证
* @param token 登录的信息
* @return 认证信息,认证成功后返回
* @throws AuthenticationException 失败抛出的异常,可自定义异常实现错误信息抛出。因为返回值中不包含错误信息,Shiro使用异常来传递认证失败的信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//视传入token而定,这里传入的是UsernamePasswordToken
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//获取用户的逻辑
User user = AccountInfoData.getUser(userToken.getUsername());
//用户不存在,抛出异常
if (user == null)throw new AuthenticationException("用户不存在");
//密码错误,抛出异常
if (!user.getPassword().equals(userToken.getPassword()))throw new AuthenticationException("密码错误");
//认证信息类
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();
//设置用户主体,像是授权、获取登录用户信息的对象就是在这里传入的
info.setPrincipals(new SimplePrincipalCollection(user,getName()));
//这里传入密码是为了修改密码后认证信息失效
info.setCredentials(user.getPassword());
return info;
}
}
3.2 实现登录、授权、等其他用例
创建一个Controller,模拟登录
@RestController
public class TestController {
/**
* 登录用例
* @param loginName 登录名
* @param password 密码
* @return 结果信息
*/
@GetMapping("login")
public String login(String loginName,String password){
Subject subject = SecurityUtils.getSubject();
if (!StringUtils.hasText(loginName)){
return "用户名不能为空";
}
if (!StringUtils.hasText(password)){
return "密码不能为空";
}
//创建一个用户名密码令牌
UsernamePasswordToken token = new UsernamePasswordToken(loginName,password);
try{
subject.login(token);
}catch (AuthenticationException e){
return e.getMessage();
}
return "成功";
}
/**
* 模拟登录页面
* @return 结果信息
*/
@GetMapping("loginView")
public String loginView(){
return "请登录";
}
/**
* 用户信息查看,需要认证
* @return
*/
@GetMapping("userInfo")
public String userLoginName(){
Subject subject = SecurityUtils.getSubject();
return ((User)subject.getPrincipal()).getLoginName();
}
/**
* 用户写权限用例
* @return
*/
@GetMapping("userWrite")
public String userWrite(){
return "write";
}
/**
* 用户读权限用例
* @return
*/
@GetMapping("userRead")
public String userRead(){
return "read";
}
/**
* 用户角色用例
* @return
*/
@GetMapping("userRoleAdmin")
public String userRoleAdmin(){
return "admin";
}
/**
* 用户登出
* @return
*/
@GetMapping("logout")
public String logout(){
return "登出成功";
}
/**
* 权限不足
* @return
*/
@GetMapping("unauthorized")
public String unauthorized(){
return "权限不足";
}
}
3.3 记住我
实现RememberMeAuthenticationToken的类都可以实现记住我的功能。实现方法isRememberMe()为true,可以使得session过期的情况下(也就是登录超时)保存principal,但isAuthenticated()会为false。配合user过滤器可实现登录一次无需登录。使用rememberMe时principals存储的必须是可序列化的。
3.4 Shiro配置
3.4.1 过滤器链配置
Shiro自带的13个过滤器:
名称 | 类 | 说明 |
---|---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名过滤器。不对请求做任何操作 |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 认证过滤器。对未认证的请求进行自动登录。会创建一个UsernamePasswordToken的token供Realm使用,参数从请求的参数中获取 |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | 基本认证过滤器,有参。例:/basic/** = authcBasic[POST],对POST方法进行认证。其他功能同authc,但是token的参数是从请求头获取 |
authcBearer | org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter | 令牌认证过滤器,有参。例:/bearer/** = authcBearer[POST],对POST方法进行认证。其他功能同authc,但是Token创建的是BearerToken类型。BearerToken只带有一个String类型的属性,名称是token。 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 登出过滤器。登出后重定向到 ‘/’。 |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter | 不需要创建Session过滤器。在操作一次后过期情景下使用。 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 权限过滤器,有参。例:perms[read],拥有read权限的才可访问。 |
port | org.apache.shiro.web.filter.authz.PortFilter | 端口过滤器,有参。例:port[8080],访问端口必须为8080. |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | rest风格权限过滤器,有参。例:rest[user],get方法会将权限翻译为user:read。 |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色过滤器,有参。例:roles[admin],用户admin角色才可访问。 |
ssl | org.apache.shiro.web.filter.authz.SslFilter | https过滤器。协议名必须为https。 |
user | org.apache.shiro.web.filter.authc.UserFilter | 用户过滤器。比认证过滤器宽松,只需要Subject的principal不为空。 |
invalidRequest | org.apache.shiro.web.filter.InvalidRequestFilter | 无效请求过滤器。默认的全局过滤器,过滤无效请求。 |
创建一个Spring @Configuration注解类ShiroConfig,添加Shiro过滤链bean
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
//登录 不需要认证
chain.addPathDefinition("/login","anon");
//登录页面 不需要认证
chain.addPathDefinition("/loginView","anon");
//read权限才能访问
chain.addPathDefinition("/userRead","perms[read]");
//write权限才能访问
chain.addPathDefinition("/userWrite","perms[write]");
//登出
chain.addPathDefinition("/logout","logout");
// 所有uri 需要登录
chain.addPathDefinition("/**","user");
return chain;
}
}
这里对于"/**"为什么使用user过滤器而不使用auth*类的过滤器呢?一方面是我们不需要自动登录,token由我们自己创建。一方面在安全性上auth是大于user的,我们如果设置UsernamePasswordToken的rememberMe为true的话,由于auth验证的是Subject.isAuthenticated(),当Session过期了,记住我这个功能就失效了。所以我们选择user作为我们的认证过滤器。
3.4.2 注册Realm
@Bean
public Realm realm(){
return new MyRealm();
}
3.4.3 其他配置
application.yml加上:
shiro:
loginUrl: /loginView #未登录跳转url
unauthorizedUrl: /unauthorized #权限不足跳转url
或 application.properties加上:
shiro.loginUrl= /loginView #未登录跳转url
shiro.unauthorizedUrl= /unauthorized #权限不足跳转url
3.5 测试
因为没写页面,我们直接用postman来测试。
为了体现差异,我们新增一个没有任何权限的用户super,添加在AccountInfoData的static块中
String user2 = "super";
users.put(user2,new User(user2,"123456"));
结果:
认证用户 | url | 结果 |
---|---|---|
- | /userInfo,/userRead,/userWrite,/userRoleAdmin | 请登录 |
- | /login?loginName=admin&password=123456,/login?loginName=super&password=123456 | 成功 |
admin | /userInfo,/userRead,/userWrite,/userRoleAdmin | admin,read,write,admin |
super | /userInfo,/userRead,/userWrite,/userRoleAdmin | super,权限不足,权限不足,权限不足 |
admin,super | /logout | 请登录(退出登登录后跳转到登录url) |
3.6 总结
Shiro把对系统的每一个访问都抽象为一个Subject。对于Subject的login操作,它用Session来保存用户的信息,以便下一次请求无需重复登录。Shiro自身也实现了自己的Session,但在Http如果没有特别声明,使用的是Http服务器实现Session的包装。权限就是字符串间的比对,Shiro实现了一种类似文件系统的权限方式,使用 “:” 进行子父分隔,使用 “,” 进行同级分隔。例如:user:* 包括 user:read,write,“*” 代表所有授权,如果你给一个用户授权为 “*”,它会拥有所有权限
4 Shiro权限注解
Shiro除了提供过滤器形式的权限管理,还使用了Spring的aop对bean进行增强。我们可以使用注解来控制权限。
Shiro的权限注解
注解 | 作用 |
---|---|
@RequiresAuthentication | 必须认证。也就是Subject.isAuthenticted() 为true |
@RequiresUser | 必须有主体。也就是subject.getPrincipal()不为空 |
@RequiresGuest | 必须没有主体。也就是subject.getPrincipal()为空 |
@RequiresRoles | 必须有传入角色。 |
@RequiresPermissions | 必须有传入权限。 |
给/userWrite注释掉过滤器,并添加上@RequiresPermissions(“write”)
/**
* 用户写权限用例
* @return
*/
@GetMapping("userWrite")
@RequiresPermissions("write")
public String userWrite(){
return "write";
}
测试
admin用户:
super用户:
权限不足引起的报错,没有提示非常不友好,可以使用@RestControllerAdvice进行全局异常捕获
@RestControllerAdvice
public class ExceptionController {
@ExceptionHandler(UnauthorizedException.class)
public String unauthz(UnauthorizedException e){
return "权限不足";
}
}
其他注解类似,我就不一一试过去了。
5 Shiro实现多端登录
这个情景像笔者经常遇到,一般小程序开发或者app开发都需要两个端:web管理端,和用户端。所以这个时候就可以使用Shiro进行多端登录。开发之前想一想,首先必须要实现两个端的登录,而且两个端之间权限不能混淆。用户端只能访问用户端的接口,管理端只能访问管理端的接口。实现步骤如下:
- 定义登录Token,实现Realm
- 定义权限,使管理端用户只能访问管理端url,用户端用户只能访问用户端url
5.1 定义登录Token,实现Realm
管理端直接使用上面写的,下面写用户端。还是一样,我不会建表,只会用一个数据类代替,大家知道就好,数据类和数据库之类的一样,使用时替换掉就好。
5.1.1 用户端
UserToken.java
public class UserToken implements AuthenticationToken {
/**
* 手机号
*/
private String phone;
/**
* 验证码
*/
private String smsCode;
public String getPhone() {
return phone;
}
public UserToken setPhone(String phone) {
this.phone = phone;
return this;
}
public String getSmsCode() {
return smsCode;
}
public UserToken setSmsCode(String smsCode) {
this.smsCode = smsCode;
return this;
}
/**
* 不重要的方法,传不传无所谓
* @return
*/
@Override
public Object getPrincipal() {
return getPhone();
}
/**
* 会和返回的AuthenticationInfo传入Credentials做比较,不相同会报错。
* 一般传入密码,这样在修改密码成功后之前的认证会失效,但是我们这里没有密码,
* 所以使用手机号
* @return
*/
@Override
public Object getCredentials() {
return getPhone();
}
}
UserRealm.java
public class UserRealm extends AuthorizingRealm {
public UserRealm(){
//设置只处理UserToken类型
setAuthenticationTokenClass(UserToken.class);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//只接受主体是String的,防止和管理端混淆权限
if (!(principalCollection.getPrimaryPrincipal() instanceof String)){
return null;
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//只要是用户登录就给他一个user角色,后面我们使用过滤器使得用户端url只能由user角色访问
info.addRole("user");
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UserToken token = (UserToken) authenticationToken;
if (!StringUtils.hasText(token.getSmsCode()))throw new AuthenticationException("验证码错误");
String account = UserAccountInfoData.getUser(token.getPhone());
//没有此用户就创建
if (account == null){
account = token.getPhone();
UserAccountInfoData.addUser(account);
}
//用户端一般是app和小程序,设置他的session不过期,如果用户端是web,去掉这行。
SecurityUtils.getSubject().getSession().setTimeout(-1000);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(account,account,getName());
return info;
}
}
UserAccountInfoData.java
public class UserAccountInfoData {
private static HashMap<String,String> users = new HashMap<>();
public static String getUser(String str){
return users.get(str);
}
public static void addUser(String phone){
users.put(phone,phone);
}
}
5.1.2 管理端
管理端大致实现和前面一样,修改一下权限返回
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
//只接受主体是User的,防止和用户端混淆权限
if (!(pc.getPrimaryPrincipal() instanceof User)){
return null;
}
//获取认证的主体,也就是认证返回的信息
User user = (User) pc.getPrimaryPrincipal();
//创建一个存储权限信息的类
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加权限
if (!CollectionUtils.isEmpty(AccountInfoData.getPermission(user.getLoginName()))){
info.addStringPermissions(AccountInfoData.getPermission(user.getLoginName()));
}
//添加角色
if (!CollectionUtils.isEmpty(AccountInfoData.getRoles(user.getLoginName()))){
for (String role : AccountInfoData.getRoles(user.getLoginName())) {
if (!role.equals("user")){
info.addRole(role);
}
}
}
//增加system角色
info.addRole("system");
return info;
}
取消user角色,并给所有管理端账户加上system权限
5.2 定义权限
定义权限前,我们先创建两个Controller,分别是UserController和SystemController
UserController.java
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("login")
public String login(String phone,String smsCode){
Subject subject = SecurityUtils.getSubject();
if (!StringUtils.hasText(phone)){
return "手机号不能为空";
}
if (!StringUtils.hasText(smsCode)){
return "验证码不能为空";
}
//创建一个userToken
UserToken token = new UserToken(phone,smsCode);
try{
subject.login(token);
}catch (AuthenticationException e){
return e.getMessage();
}
return "用户端登录成功";
}
@GetMapping("userInfo")
public String userLoginName(){
Subject subject = SecurityUtils.getSubject();
return subject.getPrincipal().toString();
}
}
SystemController.java
@RestController
@RequestMapping("/system")
public class SystemController {
@GetMapping("login")
public String login(String loginName,String password){
Subject subject = SecurityUtils.getSubject();
if (!StringUtils.hasText(loginName)){
return "用户名不能为空";
}
if (!StringUtils.hasText(password)){
return "密码不能为空";
}
//创建一个用户名密码令牌
UsernamePasswordToken token = new UsernamePasswordToken(loginName,password);
try{
subject.login(token);
}catch (AuthenticationException e){
return e.getMessage();
}
return "管理端登录成功";
}
@GetMapping("userInfo")
public String userLoginName(){
Subject subject = SecurityUtils.getSubject();
return ((User)subject.getPrincipal()).getLoginName();
}
}
基本完成,开始配置过滤器
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
//登录 不需要认证
chain.addPathDefinition("/system/login","anon");
chain.addPathDefinition("/user/login","anon");
//登出
chain.addPathDefinition("/logout","logout");
// 管理端url 需要登录和角色system
chain.addPathDefinition("/system/**","user,roles[system]");
// 用户端url 需要登录和角色user
chain.addPathDefinition("/user/**","user,roles[user]");
return chain;
}
别忘了把Realm注册到容器
@Bean
public Realm realm(){
return new MyRealm();
}
@Bean
public Realm userRealm(){
return new UserRealm();
}
5.3 测试结果
用户类型 | 用户名 | url | 结果 |
---|---|---|---|
用户端 | 13123456789 | /system/userInfo,/user/userInfo | 权限不足,13123456789 |
管理端 | admin | /system/userInfo,/user/userInfo | admin,权限不足 |
用户端 | 未登录 | /user/userInfo | 请登录 |
管理端 | 未登录 | /system/userInfo | 请登录 |
注意,在未登录的情况下,查询用户信息都会跳转到登录页,这在一端的情况下是正确的。但使用了多端,登录页也不止一个,可shiro配置只有一个,权限不足跳转的url设置也有同样的问题。
5.4 因为全局url设置导致多端跳转不正确
解决方法是自定义过滤器,并单独设置过滤器的跳转url。
6 自定义过滤器
shiro过滤器:
看到这么多也不用怕,其实我们接触到的就那么几个,其他的都是为了灵活和组件化设计的。省略掉不重要的:
这些基本上都在3.3.1上的表有过,基本功能也大致清楚。如果我们要实现多端不同跳转我们就要实现user对应的过滤器UserFilter:
UserFilter继承自AccessControlFilter。AccessControlFilter中有一个名为loginUrl的属性,在yml文件设置后会覆盖到全局。之前我们在yml文件中写的loginUrl为/loginView,所以一但未登录就会跳转到/loginView。
大概步骤如下
- 编写两个继承UserFilter的类,并修改跳转url
- 实现两端的登录页
- 将过滤器添加到容器并修改过滤链
6.1 编写两个继承UserFilter的类,并修改跳转url
UserUserFilter.java
public class UserUserFilter extends UserFilter {
public UserUserFilter(){
super.setLoginUrl("/user/loginView");
}
}
SystemUserFilter.java
public class SystemUserFilter extends UserFilter {
public SystemUserFilter(){
super.setLoginUrl("/system/loginView");
}
}
6.2 实现两端的登录页
UserController.java
@GetMapping("loginView")
public String loginView(){
return "用户登录页";
}
SystemController.java
@GetMapping("loginView")
public String loginView(){
return "管理登录页";
}
6.3 将过滤器添加到容器并修改过滤链
ShiroConfig.java
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
//登录 不需要认证
chain.addPathDefinition("/system/login","anon");
chain.addPathDefinition("/user/login","anon");
chain.addPathDefinition("/system/loginView","anon");
chain.addPathDefinition("/user/loginView","anon");
//登出
chain.addPathDefinition("/logout","logout");
// 管理端url 需要登录和角色system
chain.addPathDefinition("/system/**","suser,roles[system]");
// 用户端url 需要登录和角色user
chain.addPathDefinition("/user/**","uuser,roles[user]");
return chain;
}
@Autowired
protected SecurityManager securityManager;
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
filterFactoryBean.setSecurityManager(securityManager);
//过滤链默认过滤器
filterFactoryBean.setGlobalFilters(Collections.singletonList(DefaultFilter.invalidRequest.name()));
//过滤链
filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
HashMap filterMap = new HashMap();
filterMap.put("uuser",new UserUserFilter());
filterMap.put("suser",new SystemUserFilter());
//过滤器
filterFactoryBean.setFilters(filterMap);
return filterFactoryBean;
}
6.4 测试
用户类型 | 用户名 | url | 结果 |
---|---|---|---|
用户端 | 未登录 | /user/userInfo | 用户登录页 |
管理端 | 未登录 | /system/userInfo | 管理登录页 |
6.5 不实现类,定义相同的过滤器
想刚刚那种情况,没有对逻辑进行修改,只是修改了属性,可以不用重写一个类。定义相同类不同名称的过滤器。
ShiroConfig.java
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
filterFactoryBean.setSecurityManager(securityManager);
//过滤链默认过滤器
filterFactoryBean.setGlobalFilters(Collections.singletonList(DefaultFilter.invalidRequest.name()));
//过滤链
filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
HashMap filterMap = new HashMap();
/*filterMap.put("uuser",new UserUserFilter());
filterMap.put("suser",new SystemUserFilter());*/
UserFilter uuser = new UserFilter();
uuser.setLoginUrl("/user/loginView");
UserFilter suser = new UserFilter();
suser.setLoginUrl("/system/loginView");
filterMap.put("uuser",uuser);
filterMap.put("suser",suser);
//过滤器
filterFactoryBean.setFilters(filterMap);
return filterFactoryBean;
}
测试结果与之前一样,但是这种实现方法更为简单了。
Shiro过滤器的类别很多,但殊途同归,目的都是为了对认证和权限进行控制。提供的默认过滤器在日常使用肯定没问题,如果有其他需求可选择性的重写方法,自己实现自己的独特过滤器。
LogoutFilter:登出后默认重定向到 ‘/’ url,可以按照上面的方法重新设置。