Shiro安全框架:认证

Shiro 安全框架

1. 认证

1. 采用简单的对象登陆认证(SimpleAccountRealm)

public class AuthenticationTest {
    // 创建一个简单的认证 realm 也就是认证信息存放在对象中的
    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();

    @Before
    public void addUser(){
        simpleAccountRealm.addAccount("lwen", "1234");
    }
    @Test
    public void authentication(){
        // 创建一个 securityManager 环境
        SecurityManager securityManager = new DefaultSecurityManager(simpleAccountRealm);
        // 设置环境
        SecurityUtils.setSecurityManager(securityManager);
        // 获取认证主体
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("lwen", "1234");
        subject.login(token);
        System.out.println(subject.isAuthenticated());

    }
}

2. 简单的角色认证

public class AuthorizationTest {
    // 创建一个简单的认证 realm 也就是认证信息存放在对象中的
    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();

    @Before
    public void addUser(){
        simpleAccountRealm.addAccount("lwen", "1234","user","admin");
    }
    @Test
    public void authentication(){
        // 创建一个 securityManager 环境
        SecurityManager securityManager = new DefaultSecurityManager(simpleAccountRealm);
        // 设置环境
        SecurityUtils.setSecurityManager(securityManager);
        // 获取认证主体
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("lwen", "1234");
        subject.login(token);
        subject.checkRole("user");
        subject.checkRoles("user", "admin");
    }

2. 内置 Realm 和自定义 Realm

1. 配置文件 (initRealm)

创建一个配置文件,如下

[users]
lwen=1234,admin
mark=1234,user
[roles]
admin=update,delete,select
user=select

在代码中使用 IniRealm 即可。可用下面的代码检查用户权限。

subject.checkPermission("update");
subject.checkPermission("select");

2. 数据库(JdbcRealm)

首先需要创建表,这里我们采用了默认的查询方式,但是这种查询方式是不可用的,这里只做演示用。后面会说到关于自定义 Realm ,会详细说数据库的设计。

这里我们采用了 Jdbc 默认的 sql 语句建表。

public class AuthenticationJdbcRealmTest {
    // 创建一个简单的认证 realm 也就是认证信息存放在对象中的
    JdbcRealm jdbcRealm = new JdbcRealm();

    @Test
    public void authentication(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://127.0.0.1/shiro");
        dataSource.setUsername("root");
        dataSource.setPassword("12345678");
        jdbcRealm.setDataSource(dataSource);
        jdbcRealm.setPermissionsLookupEnabled(true);//这是一个坑 ,默认权限表不进行查找
        // 创建一个 securityManager 环境
        SecurityManager securityManager = new DefaultSecurityManager(jdbcRealm);
        // 设置环境
        SecurityUtils.setSecurityManager(securityManager);
        // 获取认证主体
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("lwen", "1234");
        subject.login(token);
        System.out.println(subject.isAuthenticated());
        subject.checkPermission("update");
        subject.checkPermission("select");
    }
}

注意这里有一个坑,就是默认的情况下 Jdbc 不回去查询权限表,导致失败。所以我们需要添加上 setPermissionsLookupEnabled 让他开启。

3. 自定义 Realm

自定义 Realm 就是说我们只要继承一个 AuthorizingRealm 类,然后重写里面的认证和授权的方法就可以了,非常的简单。一个是认证的方法也就是 doGetAuthenticationInfo 另外一个就是 授权的方法 doGetAuthorizationInfo ,里很多操作这里采用了一个静态数据表示的,实际上这些是需要从数据库或者缓存中取出来然后进行逻辑判断。

public class CustomRealm extends AuthorizingRealm {
    {
        this.setName("customRealm");
    }

    // 授权  -> 权限 角色
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String userName = (String) principalCollection.getPrimaryPrincipal();
        // 这个需要使用 service 层然后调用 dao 层去数据库或者缓存中查询,然后返回结果这里为了方便采用静态数据
        Set<String> roles = getRolesByUsername(userName);
        Set<String> permissions = getPermissionsByUsername(userName);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(roles);
        authorizationInfo.setStringPermissions(permissions);
        return authorizationInfo;
    }
    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String userName = (String) authenticationToken.getPrincipal();
        String password = getPasswordByUsername(userName);
        if (password != null) {
            return new SimpleAuthenticationInfo(userName, password,"customRealm");
        }
        return null;
    }

    private String getPasswordByUsername(String userName) {
        return "1234";
    }

    private Set<String> getPermissionsByUsername(String userName) {
        return new HashSet<>(Arrays.asList("select", "delete", "update"));
    }

    private Set<String> getRolesByUsername(String userName) {
        return new HashSet<>(Arrays.asList("admin"));
    }
}

4. 密码加密加盐

HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
customRealm.setCredentialsMatcher(matcher);

在代码逻辑中加上这段,然后在 Relam 中需要自定义盐。

// 加盐(lwen)
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("lwen"));

2. 整合 Spring

1. 首先配置过滤器

也就是类似于其他的登陆授权方式都是通过拦截器或者过滤器来完成登陆的,这里我们也是这样的思路。在 web.xml 中配置过滤器,过滤所有请求,交给 shiro 处理。

<!--shiro 配置-->
<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2. 配置 beans

以前我们手动创建的 securityManager 对象以及 Realm 和 加密函数对象现在我们都交给 Spring 管理,进行自动的依赖注入和管理。

<bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiroFilter">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="login.html"/>
    <property name="unauthorizedUrl" value="403.html"/>
    <property name="filterChainDefinitions">
        <value>
            /login.html=anon
            /subLogin=anon
            /*=authc
        </value>
    </property>
</bean>

<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
    <property name="realm" ref="customRealm"/>
</bean>

<bean class="realms.CustomRealm" id="customRealm">
    <property name="credentialsMatcher" ref="matcher"/>

</bean>
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id="matcher">
    <property name="hashAlgorithmName" value="md5"/>
    <property name="hashIterations" value="1"/>
</bean>

可以看到上面我们配置了登录页的 url 和未认证页,最后比较重要的就是我们是否需要认证的页面的配置。我们需要把登录页,和登录数据提交页给排除掉,因为如果有这些的页面的话我们将会进入死循环中,没办法进行登录操作。其中 anon 就是无需认证的,而后面 authc 需要认证,这个东西是按照顺序来的。所以 /* 放在最后面匹配。

3. 编写 controller 进行认证

public class IndexController {
    @RequestMapping(value = "/subLogin", method = RequestMethod.POST,produces = "application/json;charset=utf-8")
    @ResponseBody
    public String login(User user){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            return e.getMessage();
        }
        return "登陆成功!";
    }
}

认证没有抛出异常的话我们就算认证成功了,否则的话就是认证失败。

4. 角色和权限认证

首先需要导入 aop 的注解,然后我们进行授权操作的时候只需要进行写一个注解就行了,当然也可以直接编码实现。

导入注解 jar 配置注解springmvc 中:

<!--开启 aop-->
<aop:config proxy-target-class="true"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>
<bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.11</version>
</dependency>
@RequiresRoles("admin")
@GetMapping("/testRole")
@ResponseBody
public String testRole(){
    return "testRole";
}

@RequiresPermissions("select")
@GetMapping("/testPermission")
@ResponseBody
public String testPermission(){
    return "has";
}

3. 自动登录

要创建一个 cookie 对象,然后在自动登录管理器中注入 cookie ,接着需要传递给 securityManager。

<bean class="org.apache.shiro.web.mgt.CookieRememberMeManager" id="rememberMeManager">
    <property name="cookie" ref="cookie"/>
</bean>
<bean class="org.apache.shiro.web.servlet.SimpleCookie" id="cookie">
    <constructor-arg value="rememberMe"/>
    <property name="maxAge" value="200000"/>
</bean>
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
	<property name="realm" ref="customRealm"/>
	<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>

接着只需要在 controller 的登陆方法中进行判断就行了。

// 设置记住我
token.setRememberMe(user.isRememberMe());

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑泽君的专栏

day32_Hibernate学习笔记_04

  缓存(Cache):是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写硬盘(永久性数...

942
来自专栏battcn

一起来学SpringBoot | 第二十六篇:轻松搞定安全框架(Shiro)

Shiro是Apache 旗下开源的一款强大且易用的Java安全框架,身份验证、授权、加密、会话管理。 相比 SpringSecurity 而言 Shiro 更...

3032
来自专栏IT技术精选文摘

Spring Boot使用过滤器和拦截器分别实现REST接口简易安全认证

5962
来自专栏java、Spring、技术分享

记一次unable to create new native thread错误处理过程

unable to create new native thread,看到这里,首先想到的是让运维搞一份线上的线程堆栈(可能通过jstack命令搞定的)。...

5201
来自专栏技术小讲堂

ASP.NET AJAX(10)__Authentication ServiceAuthentication ServiceAuthentication Service属性Authentication

在通常情况下,如果使用AJAX方式调用WebService,则可能被恶意用户利用,造成性能以及安全性的问题,所以我们需要使用一些验证方式来保护WebServic...

3789
来自专栏linux驱动个人学习

Linux分页机制之分页机制的实现详解--Linux内存管理(八)

前面我们提到Linux内核仅使用了较少的分段机制,但是却对分页机制的依赖性很强,其使用一种适合32位和64位结构的通用分页模型,该模型使用四级分页机制,即

1683
来自专栏XAI

SpringMVC+MongoDB+Maven整合(微信回调Oauth授权)

个人小程序。里面是基于百度大脑 腾讯优图做的人脸检测。是关于人工智能的哦。 2017年第一篇自己在工作中的总结文档。土豪可以打赏哦。 https://git.o...

7277
来自专栏IT笔记

SpringBoot开发案例之微信小程序文件上传

前言 最近在做一个口语测评的小程序服务端,小程序涉及到了音频文件的上传,按理说应该统一封装一个第三方上传接口服务提供给前段调用,但是开发没有那么多道理,暂且为了...

4146
来自专栏函数式编程语言及工具

Akka-Cluster(0)- 分布式应用开发的一些想法

  当我初接触akka-cluster的时候,我有一个梦想,希望能充分利用actor自由分布、独立运行的特性实现某种分布式程序。这种程序的计算任务可以进行人为的...

1233
来自专栏IT笔记

SpringBoot开发案例之整合Dubbo消费者

有人卖就有人买,显然是亘古不变的真理,前两篇讲解了SpringBoot+Dubbo的提供者的几种暴露方式,这篇跟大家分享一下消费者如何去订阅属于自己的服务。 相...

3245

扫码关注云+社区