Apache Shiro用法初探

[每写一次博客,都是对自己学习的一个总结,也希望能帮到遇到同样问题的人]

首先说一下why apache shiro

最近在学着从前端到后端做一个网站来玩,那要搭建一个网站,用户和权限系统肯定是很重要的了。首先是权限系统,可以自己实现一个简单的控制,也可以使用开源的框架。由于自己是学习阶段,所以还是参考开源的框架比较好。网上搜索过后,目前用的比较多的两个开源框架就是apache shiro和spring-security。 Spring-security的功能比较强大,但是配置比较麻烦,显然,这两者是矛盾的。shiro配置比较简单,虽然功能没那么强大,但是也提供了很多可以自己定制的接口。经过两者的对比,决定还是选用shiro。2016-05-07
Version 1.0

这是第一阶段,只实现了基本的用户验证功能。

首先,pom配置,这个就不用多说了。

<span style="font-family: Arial, Helvetica, sans-serif;">     <dependency></span>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.2</version>
    </dependency>

接下来Spring配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
       <bean id="lifecyleBeanPostProcessor"
             class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

       <bean id="shiroDbRealm" class="org.rikey.web.biz.shiro.ShiroDBRealm">                   // 配置shiro的权限管理器为DB数据库的权限管理器,<span style="color:#ff0000;">需要自己实现</span>,后面会讲
              <property name="credentialsMatcher">                                             // 配置密码校验器
                     <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                            <property name="hashAlgorithmName" value="SHA-256"/>
                            <property name="storedCredentialsHexEncoded" value="false"/>
                     </bean>
              </property>
       </bean>

       <bean id="cacheManager"
             class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>

       <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">         // security manager
              <property name="realm" ref="shiroDbRealm"/>
              <property name="cacheManager" ref="cacheManager"/>
       </bean>
       <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">             // 权限过滤器,很多都见名知意了
              <property name="securityManager" ref="securityManager"/>
              <property name="loginUrl" value="/login.htm"/>
              <property name="unauthorizedUrl" value="/403.htm"/>
              <property name="filterChainDefinitions">
                     <value>
                            /login*=anon                               // anon 匿名, authc 需要鉴权
                            /dologin*=anon
                            /register.htm=anon
                            /doregister=anon
                            /logout*=anon
                            /css/**=anon
                            /js/**=anon
                            /user/**=anon
                            /**=authc

                     </value>
              </property>
       </bean>
       <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>                  // 这两个照着配就行了
       <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
              <property name="securityManager" ref="securityManager"/>
       </bean>

</beans>

第三步,web.xml,这里面还需要配置

<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecyle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

配置shiro的filter,对所有的uri进行过滤

第四步:接下来,就需要编码了。

1.注意到前面使用了一个ShiroDBRealm,这个是我们自己根据shiro的接口实现的一个基于数据库的鉴权授权类。

2.另外,shiro只提供了鉴权授权,对于用户注册的时候,密码的保存机制,必须和ShiroDBRealm中用到的密码加密机制一样。(当然,你可以保存明文,就不需要特殊处理用户注册时候的密码了)

1. ShiroDBRealm

package org.rikey.web.biz.shiro;

import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.rikey.web.biz.shiro.result.UserLoginResult;
import org.rikey.web.dao.UserDao;
import org.rikey.web.domain.User;

import javax.annotation.Resource;

public class ShiroDBRealm extends AuthorizingRealm {

    @Resource(name = "userDao")
    private UserDao userDao;

    private static final String REALM_NAME = "shiroDbRealm";

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName = (String)super.getAvailablePrincipal(principals);

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        Subject currentUser = SecurityUtils.getSubject();

        if (null != currentUser) {
            Session session = currentUser.getSession();
            if (session != null) {
                UserLoginResult user = (UserLoginResult)session.getAttribute("currentUser");
                authorizationInfo.addStringPermissions(user.getPermissions());
                return authorizationInfo;
            }
        }

        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
        String userName = usernamePasswordToken.getUsername();
        User user = userDao.queryUser(userName);

        if (user == null || user.getPassword() == null) {
            return null;
        }

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), REALM_NAME);

        return authenticationInfo;
    }
}

doGetAuthorizationInfo方法先不看,这个是用来获取授权信息的(用户对各个uri是否有权限的信息),这次还没涉及到这个。

重点看doGetAuthencationInfo方法,调用方(shiro框架里的方法来调用)会传入一个token,里面会包含用户名,我们在这个方法中要做的事就是根据框架传进来的用户名,到数据库中去获取该用户的密码,salt值,然后封装成一个SimpleAuthenticationInfo对象,传出去就行了,其他事情就交给我们前面在spring中配置的HashedCredentialsMather来做了。(其实到这里,你可以发现了,shiro里面很多东西,我们都可以自己实现的,比如这个CredentialsMatcher,可以实现我们自己的密码校验逻辑,可以在里面检查密码错误次数等)。

第5步:

有了前面的基础工作,现在就可以在自己的web项目中使用shiro来进行鉴权了。

在处理登录请求的controller中:

@RequestMapping(value = {"/dologin.htm"}, method = RequestMethod.POST)
    public String doLogin(String userName, String password,Boolean remindMe, Model model) {
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        remindMe = remindMe == null ? false : true;
        token.setRememberMe(remindMe);
        Subject subject = SecurityUtils.getSubject();
        String msg;
        try {
            subject.login(token);
            if (subject.isAuthenticated()) {
                return "redirect:/";
            } else {
                model.addAttribute("msg", "用户名或密码错误");
                return "login";
            }
        } catch  (IncorrectCredentialsException e) {
            msg = "登录密码错误. Password for account " + token.getPrincipal() + " was incorrect.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (ExcessiveAttemptsException e) {
            msg = "登录失败次数过多";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (LockedAccountException e) {
            msg = "帐号已被锁定. The account for username " + token.getPrincipal() + " was locked.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (DisabledAccountException e) {
            msg = "帐号已被禁用. The account for username " + token.getPrincipal() + " was disabled.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (ExpiredCredentialsException e) {
            msg = "帐号已过期. the account for username " + token.getPrincipal() + "  was expired.";
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (UnknownAccountException e) {
            msg = "帐号不存在. There is no user with username of " + token.getPrincipal();
            model.addAttribute("msg", msg);
            System.out.println(msg);
        } catch (UnauthorizedException e) {
            msg = "您没有得到相应的授权!" + e.getMessage();
            model.addAttribute("msg", msg);
            System.out.println(msg);
        }

        return "login";
    }

首先,注意到代码中有一句: Subject subject = SecurityUtils.getSubject();    SecurityUtils是Shiro的一个工具类,可以获取到很多跟用户登录信息相关的东西。

当前获取了一个subject,这个subject可以获取当前用户的登录信息等,这里直接调用subject的login就行了。

注意login的参数,是一个UserPasswordToken,看到没,和我们前面在ShiroDBRealm中校验密码时用到的UserPasswordToken是同一个类。也就是说,后面校验密码时的对象,是从这里传进去的。

OK,到了这里,好像大功告成了。

纳尼,数据库中还没有用户数据?这可没法玩啊,也没法校验我们写的这么多代码是不是OK的。

if  你只是为了测验一下

可以直接在ShiroDBRealm的获取鉴权信息那个方法中将用户名、加密后的密码、salt写死返回就行了。

else

接下来就需要实现一个注册用户的controller,将用户数据写入数据库。

package org.rikey.web.controller;

import org.apache.shiro.crypto.hash.Sha256Hash;
import org.rikey.web.dao.UserDao;
import org.rikey.web.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.annotation.Resource;
import java.util.Random;

@Controller
public class Register {

    @Resource(name = "userDao")
    private UserDao userDao;

    private static final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    private static final int SALT_LENGTH = 6;

    @RequestMapping(value = "/register.htm", method = RequestMethod.GET)
    public String register(){
        return "register";
    }

    @RequestMapping(value = {"/doregister"}, method = RequestMethod.POST)
    public String doregister(User user, Model model) {

        try {
            String password = user.getPassword();

            Random random = new Random();
            random.setSeed(System.nanoTime());

            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < SALT_LENGTH; i ++) {
                int number = random.nextInt(CHARS.length());
                sb.append(CHARS.charAt(number));
            }

            String salt = sb.toString();
            String hashedPasswordBase64 = new Sha256Hash(password, salt).toBase64();
            user.setPassword(hashedPasswordBase64);
            user.setSalt(salt);

            userDao.addUser(user);
            return "redirect:/login.htm";
        } catch (Exception e) {
            model.addAttribute("errormsg", "添加用户失败");
            return "error";
        }

    }
}

OK,注意,这里没有用到任何shiro框架结构,只用到了shiro的一个计算hash密码的方法。具体采用什么方法是跟前面的密码校验算法有关的,所以这里可以抽取出一个公共类,做成策略模式,在这里调用。

看前面的shiro spring配置,密码校验是采用的sha256算法,所以我们这里也采用Sha256Hash来进行加密。注意salt是随机生成的。OK,把这些数据写入数据库就行了。

至于数据库中User表的结构就不用多说了吧?id,用户名,密码,salt这些是必须的,其他的什么最近登录时间、错误重试次数等等,你能想到必须的都可以写进去。

时间: 2024-10-03 02:35:10

Apache Shiro用法初探的相关文章

SpringMVC+Apache Shiro+JPA(hibernate)案例教学(四)基于Shiro验证用户权限,且给用户授权

最新项目比较忙,写文章的精力就相对减少了,但看到邮箱里的几个催更,还是厚颜把剩下的文档补上. 一.修改ShiroDbRealm类,实现它的doGetAuthorizationInfo方法 package org.shiro.demo.service.realm; import java.util.ArrayList; import java.util.List; import javax.annotation.Resource; import org.apache.commons.lang.St

【Shiro】Apache Shiro架构之权限认证(Authorization)

上一篇博文总结了一下Shiro中的身份认证,本文主要来总结一下Shiro中的权限认证(Authorization)功能,即授权.如下: 本文参考自Apache Shiro的官方文档:http://shiro.apache.org/authorization.html. 本文遵循以下流程:先介绍Shiro中的权限认证,再通过一个简单的实例来具体说明一下API的使用(基于maven). 1. 权限认证的核心要素 权限认证,也就是访问控制,即在应用中控制谁能访问哪些资源.在权限认证中,最核心的三个要素

Apache Shiro 使用手册(一)Shiro架构介绍

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

在 Web 项目中应用 Apache Shiro

Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证.授权.加密.会话管理等功能.认证和授权为权限控制的核心,简单来说,"认证"就是证明你是谁? Web 应用程序一般做法通过表单提交用户名及密码达到认证目的."授权"即是否允许已认证用户访问受保护资源.关于 Shiro 的一系列特征及优点,很多文章已有列举,这里不再逐一赘述,本文重点介绍 Shiro 在 Web Application 中如何实现验证码认证以及如何实现单点登录. 用户权限模型

拦截404页面时tomcat抛出异常: org.apache.shiro.UnavailableSecurityManagerException

404页面中包含shiro标签,当访问404页面时,抛出异常: 原因:shiro拦截器配置缺少 标红部分,缺少红色部分导致在serverlet在拦截404页面的时候没有经过shiro  从而使shiro标签解析失败引发错误. <filter-mapping>    <filter-name>shiroFilter</filter-name>    <url-pattern>/*</url-pattern> <dispatcher>RE

Apache Shiro系列(1)

Apache Shiro是啥呢,安全框架. 360百科是这么描述的:        Apache Shiro(日语"堡垒(Castle)"的意思)是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用.移动应用到大型网络及企业应用. Shiro为解决下列问题(我喜欢称它们为应用安全的四要素)提供了保护应用的API: 认证 - 用户身份识别,常被称为用户"登录"; 授权 - 访问控制; 密码加密 - 保护或隐

Apache Shiro 使用手册(二)Shiro 认证

认证就是验证用户身份的过程.在认证过程中,用户需要提交实体信息(Principals)和凭据信息(Credentials)以检验用户是否合法.最常见的"实体/凭证"组合便是"用户名/密码"组合. 一.Shiro认证过程 1.收集实体/凭据信息 Java代码   //Example using most common scenario of username/password pair: UsernamePasswordToken token = new Userna

Apache Shiro系列四:Shiro的架构

Shiro的设计目标就是让应用程序的安全管理更简单.更直观. 软件系统一般是基于用户故事来做设计.也就是我们会基于一个客户如何与这个软件系统交互来设计用户界面和服务接口.比如,你可能会说:“如果用户登录了我们的系统,我就给他们显示一个按钮,点击之后可以查看他自己的账户信息.如果没有登录,我就给他显示一个注册按钮.” 上述应用程序在很大程度上是为了满足用户的需求而编写的,即便这个“用户”不是人,而是一个其他的软件系统.你仍然是按照谁当前正在与你的系统交互的逻辑来编写你的逻辑代码. Shiro的设计

Apache Shiro学习笔记(九)Spring集成

鲁春利的工作笔记,好记性不如烂笔头 Integrating Apache Shiro into Spring-based Applications Shiro 的组件都是JavaBean/POJO 式的组件,所以非常容易使用Spring进行组件管理,可以非常方便的从ini配置迁移到Spring进行管理,且支持JavaSE应用及Web 应用的集成. Web Applications 1.web.xml <!-- The filter-name matches name of a 'shiroFil