跟我学Shiro实践-简单的认证授权

本文是基于张开涛老师的跟我学Shiro系列的个人实践。几个月前过了一遍张开涛的跟我学Shiro系列,因为没有实践,基本上又全部还给开涛老师了。趁着假期,这次准备将开涛老师的讲解实践一遍。当然本人的实践不会与开涛的实例完全相同,不然就没必要在写一遍了。

本文只会对相关必要的Shiro概念说明下,建议有时间可以阅读下开涛的Shiro系列:http://jinnianshilongnian.iteye.com/blog/2018398

Shiro的架构和简单的认证授权

1.1 Shiro架构和组件介绍

Apache Shiro是Java的一个权限安全框架,与之类似的还有Spring Security,如果只是简单应用的话我还是比较推荐Shiro的,因为Spring Security的集成我总感觉是太过复杂。

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服务器);

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

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

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

1.2 认证和授权的流程

认证:

1、创建SecurityManager环境

2、Subject主体提交认证

3、SecurityManager认证

4、Authenticator认证器认证

5、Realm验证

授权:

1、创建SecurityManager环境

2、Subject主体提交授权

3、SecurityManager授权

4、Authrizer授权

5、Realm访问数据库获取权限数据

maven依赖

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
</dependencies>

1.3  SimpleAccountRealm认证代码

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

public class LoginTest {
    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
    @Before
    public void addUser() {
        simpleAccountRealm.addAccount("laoyeye", "123456");
    }

    @Test
    public void testAuthentication() {
        // 1.构建SecurityManager环境
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(simpleAccountRealm);

        // 2.Subject主体提交认证
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("laoyeye", "123456");
        subject.login(token);
        System.out.println("是否登录:" + subject.isAuthenticated());
        subject.logout();
        System.out.println("是否登录:" + subject.isAuthenticated());
    }
}

结果:

是否登录:true
是否登录:false

我们上面在上面流程中说有五步,代码中怎么只看到三步就登陆成功了呢,这里我们看下源码中内容就容易理解了。

public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        Subject subject = securityManager.login(this, token);
        PrincipalCollection principals;

        String host = null;

        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //we have to do this in case there are assumed identities - we don‘t want to lose the ‘real‘ principals:
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            String msg = "Principals returned from securityManager.login( token ) returned a null or " +
                    "empty value.  This value must be non null and populated with one or more elements.";
            throw new IllegalStateException(msg);
        }
        this.principals = principals;
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
            this.host = host;
        }
        Session session = subject.getSession(false);
        if (session != null) {
            this.session = decorate(session);
        } else {
            this.session = null;
        }
    }

首先我们的主体subject.login(token)的时候提交了认证,SecurityManager开始进入认证,然后进入到下面这里。

   public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

Authenticator认证器开始认证工作。接着往下走。。

    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }

可以看到我们的Authenticator开始去取我们代码刚开始设置的Realm数据了,然整个流程就走通了。

授权

接着上面的代码,我们添加下面红色标注的一行代码

public class LoginTest {
    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
    @Before
    public void addUser() {
        simpleAccountRealm.addAccount("laoyeye", "123456","admin");
    }

    @Test
    public void testAuthentication() {
        // 1.构建SecurityManager环境
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(simpleAccountRealm);

        // 2.Subject主体提交认证
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("laoyeye", "123456");
        subject.login(token);
        System.out.println("是否登录:" + subject.isAuthenticated());

        subject.checkRoles("admin");    //授权

        subject.logout();
        System.out.println("是否登录:" + subject.isAuthenticated());
    }
}

结果:

org.apache.shiro.authz.UnauthorizedException: Subject does not have role [admin]
    at org.apache.shiro.authz.ModularRealmAuthorizer.checkRole(ModularRealmAuthorizer.java:421)
    at org.apache.shiro.authz.ModularRealmAuthorizer.checkRoles(ModularRealmAuthorizer.java:440)
    at org.apache.shiro.mgt.AuthorizingSecurityManager.checkRoles(AuthorizingSecurityManager.java:173)
    at org.apache.shiro.subject.support.DelegatingSubject.checkRoles(DelegatingSubject.java:250)
    at com.allan.test.LoginTest.testAuthentication(LoginTest.java:33)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:69)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:48)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:292)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

报错说没有admin用户,这时候我们在

 simpleAccountRealm.addAccount("laoyeye", "123456","admin");

就正常了。

授权的流程基本上和认证差不多,大家可以自行看下源码。

1.4  JdbcRealm的实现

maven依赖

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.25</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>0.2.23</version>
        </dependency>
    </dependencies>
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

public class JdbcRealmTest {
    DruidDataSource dataSource = new DruidDataSource();
    {
        dataSource.setUrl("jdbc:mysql://xxxxxx:3306/shiro");
        dataSource.setUsername("root");
        dataSource.setPassword("xxxx");
    }
    @Test
    public void testAuthentication() {
        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setDataSource(dataSource);
        //开启角色权限
        jdbcRealm.setPermissionsLookupEnabled(true);

        //自定义SQL
        //认证查询
        jdbcRealm.setAuthenticationQuery("select password from test_users where username = ?");
        //用户角色查询
        jdbcRealm.setUserRolesQuery("select role_name from test_user_roles where username = ?");

        // 1.构建SecurityManager环境
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(jdbcRealm);

        // 2.主题提交认证请求
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("laoyeye", "123456");
        subject.login(token);
        System.out.println("是否登录:" + subject.isAuthenticated());

        subject.checkRoles("admin","user");    //授权
        subject.checkPermission("user:select");    //权限

        subject.logout();
        System.out.println("是否登录:" + subject.isAuthenticated());
    }

}

1.5 自定义Realm的实现

public class CustomRealm extends AuthorizingRealm {
    Map<String, String> userMap = new HashMap<>();
    //模拟数据库或缓存的数据
    {
//        Md5Hash md5 = new Md5Hash("123456");    //加密
        Md5Hash md5 = new Md5Hash("123456","laoyeyesalt");//加盐
        userMap.put("laoyeye", md5.toString());
//        userMap.put("laoyeye", "123456");
        super.setName("customRealm");
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        // 从数据库或者缓存中获得角色数据
        Set<String> roles = getRolesByUserName(username);
        Set<String> permissions = getPermissionsByUserName(username);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);

        return simpleAuthorizationInfo;
    }

    private Set<String> getPermissionsByUserName(String username) {
        Set<String> sets = new HashSet<>();
        sets.add("user:delete");
        sets.add("user:add");
        return sets;
    }

    private Set<String> getRolesByUserName(String username) {
        Set<String> sets = new HashSet<>();
        sets.add("admin");
        sets.add("user");
        return sets;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 1.从主体传过来的认证信息中,获得用户名
        String username = (String) token.getPrincipal();

        // 2.通过用户名到数据库中获取凭证
        String password = getPasswordByUsername(username);
        if(password == null) {
            return null;
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("laoyeye", password, "customRealm");
        //加盐
        simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("laoyeyesalt"));
        return simpleAuthenticationInfo;
    }

    private String getPasswordByUsername(String username) {
        return userMap.get(username);
    }

}
public class CustomRealmTest {

    CustomRealm customRealm = new CustomRealm();

    @Test
    public void testAuthentication() {
        // 1.构建SecurityManager环境
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(customRealm);

        //声明CustomRealm使用了Md5加密
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        matcher.setHashIterations(1);
        customRealm.setCredentialsMatcher(matcher);

        // 2.主题提交认证请求
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("laoyeye", "123456");
        subject.login(token);
        System.out.println("是否登录:" + subject.isAuthenticated());

        subject.checkRoles("admin");    //授权
        subject.checkPermission("user:delete");

        subject.logout();
        System.out.println("是否登录:" + subject.isAuthenticated());
    }

}

原文地址:https://www.cnblogs.com/laoyeye/p/9194925.html

时间: 2024-10-10 18:03:31

跟我学Shiro实践-简单的认证授权的相关文章

shiro的使用1 简单的认证

最近在重构,有空学了一个简单的安全框架shiro,资料比较少,在百度和google上能搜到的中文我看过了,剩下的时间有空会研究下官网的文章和查看下源码, 简单的分享一些学习过程: 1,简单的一些概念上的认知 2,使用认证的基本流程 3,shiro集成spring完成简单的认证流程,已实现 1 建一个maven的web项目,引入依赖   springmvc的的依赖       <dependency>             <groupId>org.springframework&

深入浅出学Shiro(一)--登录认证

ApacheShiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: Shiro为解决下列问题,提供了保护应用的API: 认证 - 用户身份识别,常被称为用户"登录": 授权 - 访问控制: 密码加密 - 保护或隐藏数据防止被偷窥: 会话管理 - 每用户相关的时间敏感的状态. 对于任何一个应用程序,Shiro都可以提供全面的安全管理服务.并且相对于其他安全框架,Shiro要简单的多. 核心概念:Subject,SecurityManager和Realms S

shiro简单的认证功能

使用静态shiro.ini文件完成认证 创建项目到爆 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.1</version> </dependency> <!-- Shiro uses SLF4J for logging. We'll use the 'sim

第一章 Shiro简介——《跟我学Shiro》(转)

目录贴:跟我学Shiro目录贴 1.1  简介 Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了.对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了. 本教程只介绍基本的Shiro使用,不会过多分析源码等,重在使用. Shiro可以非常容易的开发出足够好

SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统

SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统 一直想做一个管理系统,希望它简洁,能做一个demo使用.以后在研究学习的时候,可以在此基础上增加代码.我觉得权限管理系统很值得做,因为涉及关系数据库模式的设计,能学到很多东西.万事开头难,先做个简单的,以后再慢慢完善的.任何事情关键是要做,不能停留在想. 前端 由于之前没有多少前端编程经验,所以做起前端比较吃力.之前前端使用Bootstrap,发现需要自己编写很多前端代码,虽然花费了很多时间,但是页面做

拿nodejs快速搭建简单Oauth认证和restful API server攻略

拿nodejs快速搭建简单Oauth认证和restful API server攻略:http://blog.csdn.net/zhaoweitco/article/details/21708955 最近一直在鼓捣这个东西,拿出来分享下一下经验吧,其实很简单,一点也不难. 首先需求是这样,给自己的网站要增加API服务,API分为两种,公共的和私有授权的,授权的使用Oauth方法认证身份,API格式均为JOSN和JSONP. 嗯,别的语言我也没怎么学过,首先是找合适的框架进行实现吧.本身网站使用的e

Shiro系列之Shiro+Mysql实现用户认证(Authentication)

网上大多数介绍Apache Shiro的资料都是使用ini文件的简单配置为例,很少用讲到如何配合数据库来实现用户认证的.我也是刚刚开始接触Shiro,在这里介绍一个入门级别的Shiro+Mysql的配置方法,这个方法仅仅是个开始,并没有和Web,Spring,Mybatis等框架进行整合,后续我还会继续和大家分享我的学习过程及心得. now we can start the things that we really care about. 数据库中创建一个用户表,字段可以很简单. CREATE

源码分析shiro认证授权流程

1. shiro介绍 Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户“登录”: 授权 - 访问控制: 密码加密 - 保护或隐藏数据防止被偷窥: 会话管理 - 每用户相关的时间敏感的状态. 对于任何一个应用程序,Shiro都可以提供全面的安全管理服务.并且相对于其他安全框架,Shiro要简单的多. 2. shiro源码概况 先要了解shiro的基本框架(见http://www.cnblogs.com/davi

【Shiro】Apache Shiro架构之身份认证(Authentication)

Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理功能,可为任何应用提供安全保障.本文主要介绍一下Shiro中的身份认证功能,如下: 本文参考自Apache Shiro的官方文档:http://shiro.apache.org/authentication.html. 本文遵循以下流程:先介绍Shiro中的身份认证,再通过一个实例来具体说明一下(基于maven). 1. 认证主体(Authenticating Subjects) Subject 认证主体包