Shiro安全框架

Shiro安全框架

1.安全框架相关的知识

1.1目前主流的安全框机

1.SpringSecurity:是Spring家族的一部分,很多项目中会使用spring全家桶,相对与shiro来说,springSecurity更重量级,必须要求spring环境;相对shiro而言,功能更强大

2.shiro:轻量级的,使用很方便,灵活,是apache提供的,在任何框架的

1.2认证

认证 用来确定用户身份(常见用户名和密码组合)

1.3授权

对用户访问系统资源的行为做控制(后台接口访问、敏感数据显示等)

1.4 RBAC(role based access control)基于权限控制

用户关联角色和资源

资源—角色(多对多)

最终通过资源来控制角色的行为

2.了解Shiro

官方网站:http://shiro.apache.org/index.html

2.1Shiro简介

Shiro是java的一个安全(权限)框架。

Shrio可以非常容易开发出足够好的应用,不仅可以用在javaSE环境,也可以用在javaEE环境。

Shiro可以完成:认证、授权、加密、会话管理、与web集成、缓存等。

以下是基本功能点:
请添加图片描述

请添加图片描述

2.2Shiro架构(从外部看)*

请添加图片描述
请添加图片描述

ApplicationCode(应用程序):主要与subject打交道

Subject(当前角色):当前用户(角色)

ShrioSecurityManager(Shiro安全管理器):Shiro的核心,管理所有的subject

Realm(可拔插的):一种类型的数据
请添加图片描述

2.3Shiro架构(从内部看)

请添加图片描述

3.我的shiro框架

<!--        引入shiro框架-->
        <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.17</version>
        </dependency>

3.1Shiro配置的三步骤

package com.thh.shirodemo.config;

import org.apache.shiro.realm.Realm;
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;

/**
 * @author shkstart
 * @create 2022-07-18-0:01
 */
@Configuration
public class ShiroConfig {
    //1.realm 代表系统资源
    @Bean
    public Realm myRealm(){
        return new MyRealm();
    }
    //2.SecurityManager(做流程控制)
    @Bean
    public DefaultWebSecurityManager mySecurityManager(Realm myReam){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myReam);
        return securityManager;
    }

    //3.ShiroFilterFactoryBean 请求过滤器
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mySecurityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(mySecurityManager);
        return factoryBean;
    }
}

3.2实现登录认证的过程

3.2.1创建myRealm

继承AuthorizingRealm

实现父类doGetAuthenticationInfo认证的方法

配置路径过滤器

package com.thh.shirodemo.config;

import com.thh.shirodemo.bean.User;
import com.thh.shirodemo.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;

/**
 * @author shkstart
 * @create 2022-07-18-0:02
 */
public class MyRealm extends AuthorizingRealm {
    @Resource
    UserService userService;

    private Logger logger=LoggerFactory.getLogger(MyRealm.class);

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("shiro的授权方法");
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.info("shiro的认证方法");
        //获取当前用户
        UsernamePasswordToken userToken=(UsernamePasswordToken) token;
        String username=userToken.getUsername();
        //获得数据库中的用户来和当前用户进行比对,认证
        User user = userService.queryUserByName(username);
        //如果没有查到用户 表示没有该用户
        if(user==null){
            return null;
            //后面会抛出UnknownAccountException异常
        }
        //返回AuthenticationToken,完成认证流程
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),"myRealm");
        //shiro帮我们进行密码认证(密码敏感 已经包装好了)
        return simpleAuthenticationInfo;
    }

}

3.2.2配置路径过滤器

   //3.ShiroFilterFactoryBean 请求过滤器
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mySecurityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(mySecurityManager);
        //配置路径过滤器
        Map<String,String> filterMap=new HashMap<>();
        //key是ant路径 value配置shiro的默认过滤器
        //Shiro的默认过滤器,配置DefaultFilter中的key
        //auth,authc,perms,role

        //表示两个资源路径都需要登录才可以访问
        filterMap.put("/mobile/**","authc");
        filterMap.put("/salary/**","authc");
//        //配置登出
//        filterMap.put("/common/logout","logout");
        factoryBean.setFilterChainDefinitionMap(filterMap);
        return factoryBean;
    }

3.2.3登录功能

@PostMapping("/login")
public Object login(User user){
    //返回的信息
    Map<String,String> errorMsg=new HashMap<>();
    //获取当前用户
    Subject currentUser = SecurityUtils.getSubject();
    //判断是否完成认证
    if(!currentUser.isAuthenticated()) {
        //没认证
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getUserPass());
        try {
            currentUser.login(token);
            //在session中保存登陆的用户
            currentUser.getSession().setAttribute("currentUser", currentUser.getPrincipal());
            return "login Succeed";
        } catch (UnknownAccountException uae) {
            //用户不存在
            errorMsg.put("errorMsg", "用户不存在");
            log.info("There is no user with username of " + token.getPrincipal());
        } catch (IncorrectCredentialsException ice) {
            //密码不正确
            errorMsg.put("errorMsg", "密码不正确");
            log.info("Password for account " + token.getPrincipal() + " was incorrect!");
        } catch (LockedAccountException lae) {
            //账户已锁定
            errorMsg.put("errorMsg", "账户已锁定");
            log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                    "Please contact your administrator to unlock it.");
        }
        // ... catch more exceptions here (maybe custom ones specific to your application?
        catch (AuthenticationException ae) {
            //用户登录失败
            errorMsg.put("errorMsg", "用户登录失败");
            //unexpected condition?  error?
        }
    }
    errorMsg.put("已完成认证","用户为:"+currentUser.getSession().getAttribute("currentUser"));
    return errorMsg;
}

3.2.4登出功能

1.手动写接口登出

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

2.配置shiro使用shiro提供的logout过滤器

        //配置登出
        filterMap.put("/common/logout","logout");

3.3实现授权功能

目标:

1、控制主页上按钮的访问权限

currentUser.getPricipal() 来自于 MyRealm中doGetAuthenticationInfo认证 方法返回的SimpleAuthenticationInfo对象的第一个属性。

//返回AuthenticationToken,完成认证流程
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),"myRealm");

2、控制后台资源路径的访问权限

方法1、在controller中自行判断权限。

        Subject currentUser = SecurityUtils.getSubject();
//        isPermitted是myRealm中授权部分添加的内容
        if(currentUser.isPermitted("mobile")){
            return "mobile";
        }
        return "error";

方法2、使用shiro提供的perms过滤器,集中配置权限信息。

//表示两个资源路径都需要登录才可以访问
filterMap.put("/mobile/**","authc,perms[mobile]");
filterMap.put("/salary/**","authc,perms[salary]");
factoryBean.setUnauthorizedUrl("/common/unauthorized");

错误补充机制:没有权限就会进入ShiroFilterFactoryBean中配置的 UnauthorizedUrl

方法3、使用shiro提供的注解,实现方法级别的权限控制。

@RequiresAuthentication 需要完成用户登录

@RequiresGuest 未登录用户可以访问,登录用就不能访问。

@RequiresPermissions 需要有对应资源权限 @RequiresRoles 需要有对应的角色

@RequiresUser 需要完成用户登录并且完成了记住我功能。

错误补充机制:没有权限就会抛出异常。

区别和方法二的不同

此时捕获异常需要利用springboot中自定义异常

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

4.密码加密

shiro会获得一个CredentialsMatcher对象,来对密码进行比对。

想要用MD5方式进行加密:

Md5CredentialsMatcher已经过期, 要使用HashedCredentialsMatcher并设定算法名。

HashedCredentialsMatcher :

String hashAlgorithm, 对应Hash接口的实现类。 MD5 算法(迭代2次数) 123456+(盐salt)–>adrian–>jklfi

int hashIterations Hash迭代次数。

boolean hashSalted 已过时,不用设置

boolean storedCredentialsHexEncoded 设置默认的true

在ShiroConfig中设置

//设置加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密算法
matcher.setHashAlgorithmName("MD5");
//设置加密的迭代次数
matcher.setHashIterations(3);
//用该matcher加密我们的realm
myRealm.setCredentialsMatcher(matcher);

在myRealm中的认证方法中加上加密需要的盐


//加入我们需要的添加的盐用来加密
ByteSource salt = ByteSource.Util.bytes("saltAdrian");
//返回AuthenticationToken,完成认证流程
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),salt,"myRealm");

加盐加密: 需要在认证返回的认证信息SimpleAuthenticationInfo中,指定需要加的盐 salt。这样算出来的密文才可以和数据库中的密文进行比对。

解密的方法:

public class PasswordEncoder {
    public static String encoder(String password){
        //四个参数 分别是 加密方式 密码 盐 迭代次数
        SimpleHash simpleHash=new SimpleHash("MD5", ByteSource.Util.bytes(password),ByteSource.Util.bytes("saltAdrian"),3);
        return simpleHash.toString();
    }
}

5.多Realm认证(Shiro特色)

认证策略:

即一个用户可以通过 手机号或学号或用户名进行登录认证

再写一个用id来判断的realm

package com.thh.shirodemo.config;

import com.thh.shirodemo.bean.User;
import com.thh.shirodemo.mapper.UserMapper;
import com.thh.shirodemo.service.UserService;
import org.apache.log4j.lf5.LF5Appender;
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.context.annotation.Configuration;

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

/**
 * @author shkstart
 * @create 2022-07-20-22:29
 */
@Configuration
public class UserIdRealm extends AuthenticatingRealm {
   @Resource
   private UserService userService;
   @Resource
   private UserMapper userMapper;

   private Logger logger= LoggerFactory.getLogger(UserIdRealm.class);
    //实现认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.info("UserIdRealm的认证方法");
        //获取当前用户
        UsernamePasswordToken userToken=(UsernamePasswordToken) token;
        String username=userToken.getUsername();

        //获得数据库中的用户来和当前用户进行比对,认证
        User user = userService.queryUserById(Integer.parseInt(username));
        //如果没有查到用户 表示没有该用户
        if(user==null){
            return null;
            //后面会抛出UnknownAccountException异常
        }
        List<String> roles = userMapper.getRoles(user.getUserRoles());
        user.setUserPerms(roles);

        //加入我们需要的添加的盐用来加密
        ByteSource salt = ByteSource.Util.bytes("saltAdrian");
        //返回AuthenticationToken,完成认证流程

        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),salt,"myRealm");
        //shiro帮我们进行密码认证(密码敏感 已经包装好了)
        return simpleAuthenticationInfo;
    }
}

在这个类中

org.apache.shiro.authc.pam.ModularRealmAuthenticator
请添加图片描述

实现认证策略—>实行不同的认证功能

增加UserIdRealm来实现通过用户id来登录

AuthenticationStrategy接口 有三个实现类

AllSuccessfulStrategy 需要所有Realm认证成功,才能最终认证成功。

AtLeastOneSuccessfulStrategy 至少有一个Realm认证成功,才能最终认证 成功。

//设置认证策略
ModularRealmAuthenticator authenticator=new ModularRealmAuthenticator();

authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
authenticator.setRealms(Arrays.asList(myRealm,userIdRealm));
securityManager.setAuthenticator(authenticator);
return securityManager;

FirstSuccessfulStrategy 第一个Realm认证成功后即返回认证成功,不再进 行后面的Realm认证。

6.开源代码 欢迎讨论指正

git代码

个Realm认证成功,才能最终认证 成功。

//设置认证策略
ModularRealmAuthenticator authenticator=new ModularRealmAuthenticator();

authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
authenticator.setRealms(Arrays.asList(myRealm,userIdRealm));
securityManager.setAuthenticator(authenticator);
return securityManager;

FirstSuccessfulStrategy 第一个Realm认证成功后即返回认证成功,不再进 行后面的Realm认证。

6.开源代码 欢迎讨论指正

https://gitee.com/adrianwow5/ShiroDemo1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值