shiro-----Shiro与SSM集成实现用户认证和授权

目录

Shiro实现用户认证

认证流程

测试登录

盐加密

测试

Shiro授权流程?

测试

注解式开发

在控制层方法上加上注解

测试


Shiro实现用户认证

在 shiro 中,用户需要提供principals (身份)和credentials(凭证)给shiro,从而应用能验证用户身份。身份即帐号/凭证即密码

认证流程

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_17,color_FFFFFF,t_70,g_se,x_16

1、创建token令牌,token中有用户提交的认证信息即帐号和密码

2、执行Subject.login(token),Subject实例通常是DelegatingSubject类(或子类)的实例对象;在认证开始时,通过SecurityManager实例来调用securityManager.login(token)方法

3、SecurityManager接受到token(令牌)信息后委托Authenticator实例进行认证;Authenticator通过实现类ModularRealmAuthenticator来调用anthenticator.authenticate(token)方法;ModularRealmAuthenticator在认证过程中会对一个或多个Realm实例进行适配(可插拔)

4、如果配置了多个Realm,ModularRealmAuthenticator会根据配置的AuthenticationStrategy(认证策略)来进行多Realm的认证过程;在Realm被调用后,AuthenticationStrategy将对每一个Realm的结果做出响应
注意: 如果只有一个Realm,Realm将直接调用而无需再配置认证策略

5、判断每一个Realm是否都支持提交的token,如果支持,Realm调用getAuthenticationInfo(token),该方法就是实际的认证处理,我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们**自定义的认证处理

6、shiro中有三种认证策略的具体实现:**

**AtleastOneSuccessfulStrategy:**只要有一个realm验证成功,则成功
**FirstSuccessfulStrategy:**第一个realm验证成功,则成功,后续realm将被忽
**AllSuccessfulStrategy:**所有realm成功,验证才成功

认证失败后抛出的一些异常:

  • UnknownAccountException帐号不存在
  • IncorrectCredentialsException密码错误
  • DisabledAccountException帐号被禁用
  • LockedAccountException帐号被锁定
  • ExcessiveAttemptsException登录失败次数过多
  • ExpiredCredentialsException凭证过期

导入Shrio与Spring及springMVC相关依赖

<!--  shiro核心包  -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <!--  添加shiro web支持  -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <!--  添加shiro spring支持  -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>${shiro.version}</version>
    </dependency>

配置Shiro过滤器(web.xml)

  <!-- shiro过滤器定义 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
      <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>

创建自定义Realm

shiro自带的Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责授权等,但是通常情况下,正确的用户信息都是从数据库中取出,所以需要自定义realm,通常自定义的realm继承AuthorizingRealm,认证是重写**doGetAuthenticationInfo(AuthenticationToken token)方法,授权是重写doGetAuthorizationInfo(PrincipalCollection principals)**方法

package com.zking.ssm.book.shiro;

import com.zking.ssm.book.mapper.SysUserMapper;
import com.zking.ssm.book.model.SysUser;
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.springframework.beans.factory.annotation.Autowired;

import java.util.Set;

/**
 * 自定义Realm的安全数据源,采用数据库的方式
 */
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private SysUserMapper sysUserMapper;


    /**
     *授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        
    }

    /**
     * 认证
     * @param authenticationToken 传入的账号密码令牌Token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取账号
        String username = authenticationToken.getPrincipal().toString();
        //获取密码
        String password = authenticationToken.getCredentials().toString();

        //根据账号查询数据库中的用户对象信息
        SysUser sysUser = sysUserMapper.userLogin(username);
        //判断账号是否存在
        if(sysUser ==null){
            throw new UnknownAccountException("账号不存在!!!");
        }

        //创建SimpleAuthenticationInfo,传入正确的账号和密码(来自于数据库)
        SimpleAuthenticationInfo simple=new SimpleAuthenticationInfo(
                sysUser.getUsername(),
                sysUser.getPassword(),
                ByteSource.Util.bytes(sysUser.getSalt()),
                this.getName()
        );

        return simple;
    }
}

配置spring与shiro的配置文件 spring-shiro.xml

在这里配置了盐加密

<?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">

    <!--1、注册自定义的Realm 采用数据库方式-->
    <bean id="shiroRealm" class="com.zking.ssm.book.shiro.ShiroRealm">

        <!--配置Shiro明文密码如何进行加密-->
        <!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
        <!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
        <!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
        <!--以下三个配置告诉shiro将如何对用户传来的明文密码进行加密-->
        <property name="credentialsMatcher">
            <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--指定hash算法为MD5-->
                <property name="hashAlgorithmName" value="md5"/>
                <!--指定散列次数为1024次-->
                <property name="hashIterations" value="1024"/>
                <!--true指定Hash散列值使用Hex加密存. false表明hash散列值用用Base64-encoded存储-->
                <property name="storedCredentialsHexEncoded" value="true"/>
            </bean>
        </property>
    </bean>


    <!--2、创建安全管理器,并更换Realm-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--更换Realm 使用数据库方式-->
        <property name="realm" ref="shiroRealm" />
    </bean>
    <!--3、配置Shiro核心过滤器-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,这个属性是必须的 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 身份验证失败,跳转到登录页面 -->
        <property name="loginUrl" value="/home/index.html"/>
        <!-- 身份验证成功,跳转到指定页面 -->
       <!-- <property name="successUrl" value="/index.jsp"/>
        &lt;!&ndash; 权限验证失败,跳转到指定页面 &ndash;&gt;
        <property name="unauthorizedUrl" value="/noauthorizeUrl.jsp"/>-->
        <!-- Shiro连接约束配置,即过滤链的定义 -->
        <property name="filterChainDefinitions">
            <value>
                <!--anon 表示匿名访问,不需要认证以及授权-->
                <!--authc表示需要认证 没有进行身份认证是不能进行访问的-->
                /user/userLogin=anon
                /book/**=authc
                <!-- /css/**               = anon
                 /images/**            = anon
                 /js/**                = anon
                 /                     = anon
                 /user/logout          = logout
                 /user/**              = anon
                 /userInfo/**          = authc
                 /dict/**              = authc
                 /console/**           = roles[admin]
                 /**                   = anon-->
            </value>
        </property>
    </bean>

    <!--4、配置Shiro的生命周期-->
    <bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

与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">
        <!--引入mybatis与spring的集成配置-->
        <import resource="spring-mybatis.xml"/>

      

        <!--引入shiro与spring的集成配置-->
        <import resource="spring-shiro.xml"/>
</beans>

Controller层

/**
     * 跳转到登录页面
     * @return
        @RequestMapping("tologin")
        public String toLogin(){
            return "login";
        }
     */
    /**
     * 登录方法
     * @return
     */
    @RequestMapping("/userLogin")
    public String userLogin(SysUser sysuser, Model model){
        //获取主体
        Subject subject = SecurityUtils.getSubject();
        //创建账号密码令牌
        UsernamePasswordToken token = new UsernamePasswordToken(
                    sysuser.getUsername(),
                    sysuser.getPassword()
        );


        //定义错误信息
        String msg=null;
        try {
            subject.login(token);
        }catch (UnknownAccountException e) {
            msg="账号错误";
            e.printStackTrace();
        }catch (IncorrectCredentialsException e) {
            msg="密码错误";
            e.printStackTrace();
        }catch (AuthenticationException e) {
            msg="账号或密码错误";
            e.printStackTrace();
        }

        //判断msg
        if(msg == null){
            return "redirect:/toIndex";
        }else{
            model.addAttribute("msg",msg);
           // return "login";
            return "forward:/home/index.html";//转发绕开视图解析器
        }

    }


    /**
     * 安全退出
     * @return
     */
    @RequestMapping("/userLogout")
    public String userLogout(){
        //获取主体
        Subject subject = SecurityUtils.getSubject();
        subject.logout();;
        return "redirect:/home/index.html";
    }

测试登录

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

盐加密

简单的介绍一下盐加密,其实很简单就是就是在数据库中多添加了一个字段,存随机数的,相当于盐salt。

盐加密把相同的明文密码转换成不同的密文密码,而在转换成密文密码的同时用了一套盐进行加密,而在解密密文密码时需要在这个明文密码转换成密文密码的那套盐,才能进行解密。

注册时
用户 输入用户名和密码,这个时候生成salt,然后将salt和密码进行拼接,存到数据库中去,一个密码对应一个salt
登录时
用户输入用户名和密码,后台根据用户名去数据库中找到用户信息,然后将用户输入的密码和对应的盐salt进行解密,去跟存到数据库中的密码进行对比,如果一致登录成功,否则失败

上述中的shiro登陆中用到的MD5+散列1024+Hex/Base64加密方式

在这里加密还需要更改spring-shiro中的自定义Realm配置Shiro明文密码如何进行加密、解密

<!--配置Shiro明文密码如何进行加密-->
  <!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
  <!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
  <!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
  <!--以下三个配置告诉shiro将如何对用户传来的明文密码进行加密-->
  <property name="credentialsMatcher">
     <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!--指定hash算法为MD5-->
        <property name="hashAlgorithmName" value="md5"/>
        <!--指定散列次数为1024次-->
        <property name="hashIterations" value="1024"/>    
        <!--true指定Hash散列值使用Hex加密存. false表明hash散列值用用Base64-encoded存储-->
        <property name="storedCredentialsHexEncoded" value="true"/>
     </bean>
  </property>

加密密码PasswordHelper类(盐加密)

package com.zking.ssm.book.util;


import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

/**
 * 用于shiro权限认证的密码工具类
 */
public class PasswordHelper {

    /**d
     * 随机数生成器
     */
    private static RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();

    /**
     * 指定hash算法为MD5
     */
    private static final String hashAlgorithmName = "md5";

    /**
     * 指定散列次数为1024次,即加密1024次
     */
    private static final int hashIterations = 1024;

    /**
     * true指定Hash散列值使用Hex加密存. false表明hash散列值用用Base64-encoded存储
     */
    private static final boolean storedCredentialsHexEncoded = true;

    /**
     * 获得加密用的盐
     *
     * @return
     */
    public static String createSalt() {
        return randomNumberGenerator.nextBytes().toHex();
    }

    /**
     * 获得加密后的凭证
     *
     * @param credentials 凭证(即密码)
     * @param salt        盐
     * @return
     */
    public static String createCredentials(String credentials, String salt) {
        SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials,
                salt, hashIterations);
        return storedCredentialsHexEncoded ? simpleHash.toHex() : simpleHash.toBase64();
    }


    /**
     * 进行密码验证
     *
     * @param credentials        未加密的密码
     * @param salt               盐
     * @param encryptCredentials 加密后的密码
     * @return
     */
    public static boolean checkCredentials(String credentials, String salt, String encryptCredentials) {
        return encryptCredentials.equals(createCredentials(credentials, salt));
    }

    public static void main(String[] args) {
        //盐
        String salt = createSalt();
        System.out.println(salt);
        System.out.println(salt.length());
        //凭证+盐加密后得到的密码
        String credentials = createCredentials("123", salt);
        System.out.println(credentials);
        System.out.println(credentials.length());
        boolean b = checkCredentials("123", salt, credentials);
        System.out.println(b);
    }
}

测试

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

存储到数据库

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

一个密码对应一个salt

Shiro授权流程
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_17,color_FFFFFF,t_70,g_se,x_16

==============================================================================================================================================================================================================================================================================================================

1、调用授权验证方法(**Subject.isPermitted()Subject.hasRole()**等)

2、Subject实例通常是DelegatingSubject类(或子类)的实例对象;在认证开始时,通过SecurityManager实例来调用**securityManager.isPermitted(string)**方法/**security.hasRole(string)**方法

3、SecurityManager委托Authorizer的实例(默认是ModularRealmAuthorizer类的实例,同样支持多个realm)调用相应的授权方法

4、每一个Realm将检查是否实现了相同的Authorizer接口,然后调用Realm自己的相应的授权验证方法

5、使用多个Realm时,不同于认证策略处理方式,授权处理过程中:

当调用Realm出现异常时,立即抛出,结束授权验证
只要一个Realm验证成功,则认为授权成功,立即返回,结束验证

在mapper层定义两个方法

这里返回Set集合是因为在授权时,参数必须为Set集合

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

在自定义Realm中重写授权方法

/**
     *授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录账号
        String username = principalCollection.getPrimaryPrincipal().toString();
        //根据username获取用户对应角色
        Set<String> roles = sysUserMapper.findRoles(username);
        //根据username获取对应的角色权限
        Set<String> permissions = sysUserMapper.findPermissions(username);
        //创建SimpleAuthorizationInfo
        SimpleAuthorizationInfo simple=new SimpleAuthorizationInfo();
        //填充用户的角色和权限
        simple.setRoles(roles);
        simple.setStringPermissions(permissions);
        return simple;
    }

在spring-shiro.xml中的安全管理器中配置shiro的核心过滤器和并设置权限,和验证失败页面

<!--2、创建安全管理器,并更换Realm-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="shiroRealm" />
        <!--配置会话管理器-->
        <property name="sessionManager" ref="sessionManager"/>
        <!--设置缓存管理器-->
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
    <!--3、配置Shiro核心过滤器-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,这个属性是必须的 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 身份验证失败,跳转到登录页面 -->
        <property name="loginUrl" value="/home/index.html"/>
        <!-- 身份验证成功,跳转到指定页面 -->
       <!-- <property name="successUrl" value="/index.jsp"/>
        &lt;!&ndash; 权限验证失败,跳转到指定页面 &ndash;&gt;
        <property name="unauthorizedUrl" value="/noauthorizeUrl.jsp"/>-->
        <!-- Shiro连接约束配置,即过滤链的定义 -->
        <property name="filterChainDefinitions">
            <value>
                <!--anon 表示匿名访问,不需要认证以及授权-->
                <!--authc表示需要认证 没有进行身份认证是不能进行访问的-->
                /user/userLogin=anon
                /book/**=authc
                <!-- /css/**               = anon
                 /images/**            = anon
                 /js/**                = anon
                 /                     = anon
                 /user/logout          = logout
                 /user/**              = anon
                 /userInfo/**          = authc
                 /dict/**              = authc
                 /console/**           = roles[admin]
                 /**                   = anon-->
            </value>
        </property>
    </bean>

测试

在未登录下访问book模块下

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_19,color_FFFFFF,t_70,g_se,x_16

自动被权限拦截并返回登录页面watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

权限标签

导入shiro标签watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

验证身份是否是管理员

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

同时还有角色权限

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

注解式开发

1、在spring-mvc中配置shiro注解式开发

<!--9、配置Shiro注解式权限-->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"/>
    </bean>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

2.在控制层方法上加上注解

@RequiresRoles(value={“管理员”,“高级用户”},logical= Logical.OR)

在此方法上使用此权限注解,判断使用该方法是否是管理员或者高级用户

OR该文AND 为同时满足两个角色

@RequiresRoles(“管理员”)

判断使用该方法是否是管理员

/**
     * 返回List泛型格式的JSON数据
     * @param book
     * @param request
     * @return
     */
    @RequiresRoles(value={"管理员","高级用户"},logical= Logical.AND)
    @RequestMapping("/queryListBooks")
    @ResponseBody
    public List<Book> queryListBooks(Book book,HttpServletRequest request){
        PageBean pageBean=new PageBean();
        pageBean.setRequest(request);
        List<Book> books = bookService.queryBookPager(book, pageBean);
        return   books;
    };

@RequiresPermissions(“bookmanager📖edit”)

此注解表示使用此方法需要具有此权限

/**
     * 返回Map类型的JSON数据
     * @param bookId
     * @return
     */
    @RequiresPermissions("bookmanager:book:edit")
    @RequestMapping("/querySingleMap")
    @ResponseBody
    public Map<String, Object> querySingleMap(Integer bookId){
        return  bookService.querySingleMap(bookId);
    }

测试

在登录普通用户下访问

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

至此,Shiro与SSM集成实现用户认证和授权介绍完毕,由于作者水平有限难免有疏漏,欢迎留言纠错。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值