SpringBoot使用Shiro实现认证和授权,多端登录实现

本文详细介绍了如何在SpringBoot中集成和使用Apache Shiro框架,涵盖了基本概念、依赖引入、自定义Realm实现认证授权、记住我功能、权限注解、多端登录以及自定义过滤器的配置。通过实例代码展示了登录、授权、权限控制和过滤链配置,同时讨论了Shiro在多端登录场景下的应用和过滤器的定制,为实际项目开发提供了参考。
摘要由CSDN通过智能技术生成


这篇文章主要用来介绍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个过滤器:

名称说明
anonorg.apache.shiro.web.filter.authc.AnonymousFilter匿名过滤器。不对请求做任何操作
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter认证过滤器。对未认证的请求进行自动登录。会创建一个UsernamePasswordToken的token供Realm使用,参数从请求的参数中获取
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter基本认证过滤器,有参。例:/basic/** = authcBasic[POST],对POST方法进行认证。其他功能同authc,但是token的参数是从请求头获取
authcBearerorg.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter令牌认证过滤器,有参。例:/bearer/** = authcBearer[POST],对POST方法进行认证。其他功能同authc,但是Token创建的是BearerToken类型。BearerToken只带有一个String类型的属性,名称是token。
logoutorg.apache.shiro.web.filter.authc.LogoutFilter登出过滤器。登出后重定向到 ‘/’。
noSessionCreationorg.apache.shiro.web.filter.session.NoSessionCreationFilter不需要创建Session过滤器。在操作一次后过期情景下使用。
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter权限过滤器,有参。例:perms[read],拥有read权限的才可访问。
portorg.apache.shiro.web.filter.authz.PortFilter端口过滤器,有参。例:port[8080],访问端口必须为8080.
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrest风格权限过滤器,有参。例:rest[user],get方法会将权限翻译为user:read。
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter角色过滤器,有参。例:roles[admin],用户admin角色才可访问。
sslorg.apache.shiro.web.filter.authz.SslFilterhttps过滤器。协议名必须为https。
userorg.apache.shiro.web.filter.authc.UserFilter用户过滤器。比认证过滤器宽松,只需要Subject的principal不为空。
invalidRequestorg.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,/userRoleAdminadmin,read,write,admin
super/userInfo,/userRead,/userWrite,/userRoleAdminsuper,权限不足,权限不足,权限不足
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用户:
admin用户write权限测试
super用户:
在这里插入图片描述
权限不足引起的报错,没有提示非常不友好,可以使用@RestControllerAdvice进行全局异常捕获

@RestControllerAdvice
public class ExceptionController {

    @ExceptionHandler(UnauthorizedException.class)
    public String unauthz(UnauthorizedException e){
        return "权限不足";
    }
}

其他注解类似,我就不一一试过去了。

5 Shiro实现多端登录

这个情景像笔者经常遇到,一般小程序开发或者app开发都需要两个端:web管理端,和用户端。所以这个时候就可以使用Shiro进行多端登录。开发之前想一想,首先必须要实现两个端的登录,而且两个端之间权限不能混淆。用户端只能访问用户端的接口,管理端只能访问管理端的接口。实现步骤如下:

  1. 定义登录Token,实现Realm
  2. 定义权限,使管理端用户只能访问管理端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/userInfoadmin,权限不足
用户端未登录/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。
大概步骤如下

  1. 编写两个继承UserFilter的类,并修改跳转url
  2. 实现两端的登录页
  3. 将过滤器添加到容器并修改过滤链

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,可以按照上面的方法重新设置。

使用Shiro实现接口授权的具体实现步骤如下: 1. 引入ShiroSpring-boot-starter依赖: ```xml <dependencies> <!-- Shiro依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.8.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.8.0</version> </dependency> <!-- Spring-boot-starter依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> ``` 2. 配置Shiro的Filter和Realm: ```java @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); filterFactoryBean.setLoginUrl("/login"); // 设置未登录时跳转的URL filterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 设置未授权时跳转的URL // 配置拦截规则 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login", "anon"); // 登录接口放行 filterChainDefinitionMap.put("/logout", "logout"); // 注销接口放行 filterChainDefinitionMap.put("/**", "authc"); // 其他接口全部拦截,需要认证后访问 filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return filterFactoryBean; } @Bean public DefaultWebSecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public Realm realm() { // 自定义Realm实现 return new MyRealm(); } } ``` 3. 自定义Realm实现授权逻辑: ```java public class MyRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 获取当前用户信息 String username = (String) principals.getPrimaryPrincipal(); // 查询用户角色和权限信息 Set<String> roles = new HashSet<>(); Set<String> permissions = new HashSet<>(); // TODO: 查询用户角色和权限信息 // roles.add("admin"); // permissions.add("user:list"); // 封装角色和权限信息 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取用户输入的用户名和密码 String username = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); // TODO: 验证用户名和密码是否正确 // if (!"admin".equals(username)) { // throw new UnknownAccountException("用户名不存在!"); // } // if (!"admin".equals(password)) { // throw new IncorrectCredentialsException("密码错误!"); // } // 封装用户认证信息 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, getName()); return authenticationInfo; } } ``` 4. 在Controller中使用注解实现接口授权: ```java @RestController @RequestMapping("/user") public class UserController { @RequiresPermissions("user:list") @GetMapping("/list") public String list() { return "用户列表"; } @RequiresPermissions("user:add") @PostMapping("/add") public String add() { return "添加用户"; } } ``` 在Controller方法上使用@RequiresPermissions注解,指定需要的权限,如果当前用户没有该权限,则会抛出AuthorizationException异常,可以在异常处理器中统一处理。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值