shiro框架

1.shiro简介

1.1.基本功能点

Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。

· Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
· Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
· Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
· Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
· Web Support:Web 支持,可以非常容易的集成到 Web 环境;
· Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
· Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
· Testing:提供测试支持;
· Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
· Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可

1.2.Shiro的架构

1.2.1.外部

我们从外部来看 Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图

 可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;其每个 API 的含义:

Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个 Shiro 应用:

        1.应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
        2.我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

1.2.2.内部

接下来我们来从 Shiro 内部来看下 Shiro 的架构,

Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;

SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;

SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);

SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。
 

2.shiro组件

2.1.身份验证

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。

在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:

principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。

credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。

最常见的 principals 和 credentials 组合就是用户名 / 密码了。接下来先进行一个基本的身份认证。

另外两个相关的概念是之前提到的 Subject Realm,分别是主体及验证主体的数据源。
 

pom.xml 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--当前工程的坐标-->
    <groupId>com.zh</groupId>
    <artifactId>ZHSQ</artifactId>
    <!--    打包时需要的依赖-->
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
        <relativePath/>
    </parent>


    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>



    <dependencies>

        <!--		获取首字母的依赖-->
        <dependency>
            <groupId>com.belerweb</groupId>
            <artifactId>pinyin4j</artifactId>
            <version>2.5.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.6.0</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.9.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <!--mybatis依赖包-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>


        <!--lombok 依赖,子工程中假如需要lombok,不需要再引入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope><!--provided 表示此依赖仅在编译阶段有效-->
        </dependency>
        <!--单元测试依赖,子工程中需要单元测试时,不需要再次引入此依赖了-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <!--            <scope>test</scope>&lt;!&ndash;test表示只能在test目录下使用此依赖&ndash;&gt;-->
            <exclusions>
                <exclusion><!--排除一些不需要的依赖-->
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <!--其它依赖...-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.9</version>
        </dependency>

        <!--redis应用依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.6.7</version>
        </dependency>


    </dependencies>


    <build>
        <plugins>
            <!--通过maven-compiler-plugin插件设置项目
            的统一的jdk编译和运行版本-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.9.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>

                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                    <mainClass>com.zh.zhsqApplication</mainClass>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

登录/登出  controller

使用Subject currentUser = SecurityUtils.getSubject();//获取当前用户信息

能在当前任何方法中都能获取到用户的对象信息。


//登录页面   当用户输入账号密码后  会被拦截 进行认证。
//在 MyRealm 进行认证。会调用UserService进行校验账号密码。
//如果认证成功则会继续下面的操作
//当认证成功后,访问其他功能页面 则会进行授权,授权绑定
//使用注解的形式进授权访问.

@RequestMapping("/common")
@RestController
public class LoginController {

    private Logger log = LoggerFactory.getLogger(LoginController.class);

    @PostMapping("/login")
    public Object login(UserBean userBean) {  //前端传递 账号密码进行进来

        Map<String, String> errorMsg = new HashMap<>();

        Subject currentUser = SecurityUtils.getSubject();//获取当前用户信息
        if (!currentUser.isAuthenticated()) {//判断是否完成认证

            UsernamePasswordToken token = new UsernamePasswordToken(userBean.getUserName(), userBean.getUserPass());
            token.setRememberMe(true);

            try {
                currentUser.login(token);//通过抛出的异常来判断用户登录结果
                //存入session中
                currentUser.getSession().setAttribute("currentUser", currentUser.getPrincipal());//存入对象
                return "login Succeed";

            } catch (UnknownAccountException uae) { //用户不存在
                log.info("There is no user with username of " + token.getPrincipal());
                errorMsg.put("errorMsg", "用户不存在");
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
                errorMsg.put("errorMsg", "密码不正确");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
                errorMsg.put("errorMsg", "用户已锁定");
            } catch (AuthenticationException ae) {
                log.info("登录失败", ae);
                errorMsg.put("errorMsg", "登录失败");
            }
        }
            return errorMsg;
//        }else {
//
//
//        }

        }

    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(){ //获取当前用户
        Subject currentUser = SecurityUtils.getSubject();
        //获取存入session中的数据
        Session session = currentUser.getSession();
        return session.getAttribute("currentUser");
    }


    @RequestMapping("/logout")  //退出
    public void logout(){
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout();
    }


    @RequestMapping("/unauthorized")
    public String unauthorized(){

        return "未授权";
    }
}

认证和授权

授权
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

主体
主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。

资源
在应用中用户可以访问的任何东西,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面
查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)
打印文档等等。。。

如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。

Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的),后续部分介绍。

角色
角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

隐式角色:
即直接通过角色来验证用户有没有操作权限,如在应用中 CTO、技术总监、开发工程师可以使用打印机,假设某天不允许开发工程师使用打印机,此时需要从应用中删除相应代码;再如在应用中 CTO、技术总监可以查看用户、查看权限;突然有一天不允许技术总监查看用户、查看权限了,需要在相关代码中把技术总监角色从判断逻辑中删除掉;即粒度是以角色为单位进行访问控制的,粒度较粗;如果进行修改可能造成多处代码修改。

显示角色:
在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。
 

当用户登录时会进行调用认证中心进行认证。而授权是访问的接口需要权限是才会进行调用。

下面的授权代码不会一直进行授权调用,因为绑定了资源。

import com.tedu.bean.UserBean;
import com.tedu.service.UserService;
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 org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRealm extends AuthorizingRealm {  //继承  Realm的子类  授权认证
    private Logger logger = LoggerFactory.getLogger(MyRealm.class);

    @Autowired
    private UserService userService;

    @Override //授权  需要进行权限判断是时候才会去授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("entered MyRealm doGetAuthorizationInfo method");
        //获取当前用户
        UserBean user = (UserBean)principalCollection.asList().get(0);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //需要绑定角色、资源
        authorizationInfo.addRoles(user.getUserRoles());
        authorizationInfo.addStringPermissions(user.getUserPerms());
        return authorizationInfo;
    }

    @Override   //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.info("》》》》》entered MyRealm doGetAuthenticationInfo method");
        //获取当前用户信息
        UsernamePasswordToken userToken = (UsernamePasswordToken) token; //获取的是loginController中 的token
        String username = userToken.getUsername();//获取账号

        //获取数据库中的用户,进行比对,认证   shiro会帮我们比对
        UserBean userBean = userService.queryUserByName(username);
        //如果没有查到,表示没有这个用户
        if (null == userBean){
            return  null;//后面处理的过程中 会抛出 UnknownAccountException异常
        }

        //加盐  一般可以使用账号 或者指定
        ByteSource salt = ByteSource.Util.bytes("salt");
        //完成认证流程  讲对象信息传递过去     中间是加盐,不加可以直接去掉
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userBean,userBean.getUserPass(),salt,"myRealm");

        return simpleAuthenticationInfo;
    }
}

ShiroConfig   shiro必要的配置

在配置中进行配置

尤其是请求过滤器中进行配置让代码更简单。

可以设置跳转的接口路径页面等,必要要进行校验的路径等。功能和springmvc中的拦截器非常相似。


import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AllSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.AuthorizingRealm;
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.Arrays;
import java.util.HashMap;

@Configuration    //使用Shiro必要的配置
public class ShiroConfig {

//    @Bean  //代表系统资源   因为前面已经注入spring了 所以进行注掉
//    public Realm myRealm(){
//        return new MyRealm();
//    }

    //1. SecurityManager  流程控制
    @Bean                                                   //账号                            手机号引入
    public DefaultWebSecurityManager mygetSecurityManager(AuthorizingRealm myRealm,AuthorizingRealm mobileRealm){  //引用了realm对象
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //进行加密 使用MD5的方式
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");//对应hash接口的实现类
        //设置密码进行迭代的次数  更加安全  密码加盐:可以使用账号和密码拼接 然后生成md5 更安全
        matcher.setHashIterations(3);

        myRealm.setCredentialsMatcher(matcher);  //加密后 再跟数据库中的密文进行比对
//        securityManager.setRealm(myRealm);
        securityManager.setRealms(Arrays.asList(myRealm,mobileRealm));//可以通过账号 手机号进行认证

        //进行定义认证策略
        /**
         *  AllSuccessfulStrategy()   需要全部Realm认证成功,才能最终认证成功
         *  AtLeastOneSuccessfulStrategy()  至少有一个认证成功
         *  FirstSuccessfulStrategy()    第一个认证成功后即返回认证成功,后面不再进行认证
         */
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());//所有认证都必须通过
        authenticator.setRealms(Arrays.asList(myRealm,mobileRealm));

        securityManager.setAuthenticator(authenticator);
        return securityManager;
    }

    //2. ShiroFilterFactoryBean  请求过滤器
    @Bean                                                           //此处引用了上面的方法  通过spring @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mygetSecurityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(mygetSecurityManager);

        //配置路径过滤器
        HashMap<String,String> filterMap = new HashMap<>();
        //key是ant路径,value配置shiro的默认过滤器
        //shiro的默认过滤器,配置DefaultFilter 中的key
        //auth,  authc , perms , roles
        //表示两个路径 都需要登录才可以访问
//        filterMap.put("/mobile/**","authc");
//        filterMap.put("/salary/**","authc");
        //因为每次controller都要进行权限校验,所以简单点 可以在过滤器中进行校验。会跳转到未授权页面
//        filterMap.put("/salary/**","authc,perms[mobile]");
//        filterMap.put("/mobile/**","authc,perms[salary]");   后面使用了注解的形式配置权限

        //退出 也可以在此进行。另一个接口就可以不使用了
//        filterMap.put("/common/logout","logout");

        factoryBean.setFilterChainDefinitionMap(filterMap);//进行启用路径设置过滤器

        factoryBean.setLoginUrl("/index.html");  //登录页
//        factoryBean.getSuccessUrl(); //登录成功页
        factoryBean.setUnauthorizedUrl("/common/unauthorized");//未经授权页
        return factoryBean;

    }

}

 创建用户类,验证账号service.

用户User。账号,密码,权限资源等信息

public class UserBean {

    private String userName;//账号
    private String userPass;//密码
    private String userMobile;//手机号 邮箱
    private List<String> userRoles;//角色
    private List<String> userPerms;//权限

    public String getUserName() {
        return userName;
    }
    public UserBean(){

    }

    public UserBean(String userName, String userPass, List<String> userRoles, List<String> userPerms) {
        this.userName = userName;
        this.userPass = userPass;
        this.userRoles = userRoles;
        this.userPerms = userPerms;
    }


    public UserBean(String userName, String userPass,String userMobile, List<String> userRoles, List<String> userPerms) {
        this.userName = userName;
        this.userPass = userPass;
        this.userMobile=userMobile;
        this.userRoles = userRoles;
        this.userPerms = userPerms;
    }

    public String getUserPass() {
        return userPass;
    }

    public List<String> getUserRoles() {
        return userRoles;
    }

    public void setUserMobile(String userMobile){  this.userMobile=userMobile;}

    public List<String> getUserPerms() {
        return userPerms;
    }

    public String getUserMobile() {
        return userMobile;
    }
}

模拟数据库中的数据

@Component
public class TestDate {

    //模拟数据库中的数据
    private List<UserBean> allUsers;

    public List<UserBean> getAllUsers(){
        if (null == allUsers){
            allUsers = new ArrayList<>();
            allUsers.add(new UserBean("admin","admin","13888888888", Arrays.asList("admin"),Arrays.asList("mobile","salary")));
            allUsers.add(new UserBean("manager","manager", "13888888888",Arrays.asList("manager"),Arrays.asList("salary")));
            allUsers.add(new UserBean("worker","worker","13888888888", Arrays.asList("worker"),Arrays.asList("")));

        }
        return allUsers;
    }
}

验证账号密码。手机号,账号两个。用户可以通过手机号或者自己的账号登录。

import com.tedu.bean.UserBean;
import com.tedu.util.TestDate;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserService {  //得到用户账号 进行验证

    @Autowired
    private TestDate testDate;

    //根据用户名查找数据库中的用户
    public UserBean queryUserByName(String username){
        UserBean userBean = new UserBean();
        List<UserBean> queryUsers = testDate.getAllUsers().stream()
                .filter(user -> username.equals(user.getUserName()))//进行过滤 条件是名字相等
                .collect(Collectors.toList());
        //进行判断  查到数据
        if (null == queryUsers && queryUsers.size()>0){
            try {
                userBean = (UserBean) BeanUtils.cloneBean(queryUsers.get(0));
                return userBean;
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return null;
    }
//手机号查找用户信息
    public UserBean queryUserByMobile(String userMobile) {
        UserBean userBean = new UserBean();
        List<UserBean> queryUsers = testDate.getAllUsers().stream()
                .filter(user -> userMobile.equals(user.getUserMobile()))//进行过滤 条件是名字相等
                .collect(Collectors.toList());
        //进行判断  查到数据
        if (null == queryUsers && queryUsers.size()>0){
            try {
                userBean = (UserBean) BeanUtils.cloneBean(queryUsers.get(0));
                return userBean;
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return null;
    }
}

使用注解的形式设置接口的权限

MobileController

调用下面的接口时就会去验证权限是否匹配。

import org.apache.shiro.authz.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController    //资源访问
@RequestMapping("/mobile")
public class MobileController {

    //第三种权限方式(方便),第二种是在配置中进行(冗余),第一种就行每个接口进行校验非常麻烦
//    @RequiresAuthentication    需要完成用户登录
//    @RequiresGuest              未登录用户可以访问,登录后就不能访问
//    @RequiresPermissions("mobile")  需要有对应资源权限
//    @RequiresRoles("")              需要有对应的角色
//    @RequiresUser           需要完成用户登录并且完成了记住我的功能
    
    @RequiresPermissions("mobile")  //此处不需要登录的注解 因为没登录就不会获取这些数据信息
    @RequestMapping("/query")
    public String query(){
//
//        Subject currentUser = SecurityUtils.getSubject();
//        if (currentUser.isPermitted("mobile")){ //进行用户判断是否有当前的资源
//            return "mobile";
//        }
//        return "error";

        return "mobile";
    }

}
SalaryController
@RequestMapping("/salary")
@RestController
public class SalaryController {

    @RequiresPermissions("salary")
    @RequestMapping("/query")
    public String query(){
        
//        Subject currentUser = SecurityUtils.getSubject();
//        if (currentUser.isPermitted("salary")){ //进行用户判断是否有当前的资源
//            return "salary";
//        }
//        return "error";
        return "salary";
    }
}

访问后没有权限,会直接跳转到  未经授权页面

异常配置

/**
 * spring 提供的 异常处理配置
 * 提供对应的异常类型即可
 */
@RestControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(AuthorizationException.class)
    public Object shiroHandler(){
        return "请先获取对应的资源,再进行访问";
    }


}

加密加盐

在用户进行注册是就应该对密码进行加密加盐。所以比对时也要对用户输入的密码进行加密加盐

 流程控制中进行配置加密加盐验证

认证中进行的加密加盐操作

对密码进行加密加盐的方法。存入数据库时进行操作。 


import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

public class PasswordEncoder {
//密码加密  md5格式
    public static String encoder(String password){

        SimpleHash simpleHash = new SimpleHash("MD5", ByteSource.Util.bytes(password), ByteSource.Util.bytes("salt"), 3);

        System.out.println(simpleHash);
        return simpleHash.toString();
    }
}

 添加手机号认证

当用户的账号不限于一种时可以使用多个。就需要添加对应的认证

手机号认证

import com.tedu.bean.UserBean;
import com.tedu.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

@Configuration//客户可以通过手机号 进行认证       会自动寻找认证中心,有多少个找多少个。配置认证策略即可
public class MobileRealm extends AuthenticatingRealm {

    private Logger log = LoggerFactory.getLogger(MobileRealm.class);

    @Autowired
    private UserService userService;


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        log.info("》》》》》entered MyRealm doGetAuthenticationInfo method");
        //获取当前用户信息
        UsernamePasswordToken userToken = (UsernamePasswordToken) token; //获取的是loginController中 的token
        String username = userToken.getUsername();//获取账号

        //获取数据库中的用户,进行比对,认证   shiro会帮我们比对
        UserBean userBean = userService.queryUserByMobile(username);//手机号认证
        //如果没有查到,表示没有这个用户
        if (null == userBean){
            return  null;//后面处理的过程中 会抛出 UnknownAccountException异常
        }

        //加盐  一般可以使用账号 或者指定
        ByteSource salt = ByteSource.Util.bytes("salt");
        //完成认证流程  讲对象信息传递过去     中间是加盐,不加可以直接去掉
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userBean,userBean.getUserPass(),salt,"myRealm");

        return simpleAuthenticationInfo;
    }
}

认证策略

因为账号有多种,不可能让用户都需要进行认证,所以就定义认证策略。

*  AllSuccessfulStrategy()   需要全部Realm认证成功,才能最终认证成功

*  AtLeastOneSuccessfulStrategy()  至少有一个认证成功

*  FirstSuccessfulStrategy()    第一个认证成功后即返回认证成功,后面不再进行认证 

在 shiroconfig 流程控制中进行配置策略

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值