安全认证框架Shiro_认准这一篇足矣!

Shiro核心组件

  • Subject

主体。Subject在Shiro中是一个接口,接口中定义了认证授权的相关方法。程序通过调用Subject的方法进行认证授权,而Subject使用SecurityManager进行认证授权。

  • SecurityManager

权限管理器,它是Shiro的核心。通过SecurityManager可以完成具体的认证、授权等操作,SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理。SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager三个接口。

  • Authenticator

认证器。对用户登录时进行身份认证

  • Authorizer

授权器。用户认证通过后,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

  • SessionManager

会话管理。shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上。

  • Realm

领域。他是连接数据源+认证功能+授权功能的具体实现。SecurityManager通过Realm获取用户的身份和权限信息,并对用户进行认证和授权。

  • SessionDAO

会话dao,是对会话进行操作的一套接口。它可以将session数据存储到数据库或缓存服务器中。

  • CacheManager

缓存管理,将用户权限数据存储在缓存中,这样可以减少权限查询次数,提高性能。

  • Cryptography

密码管理,Shiro提供了一套加密/解密的组件,方便开发。

Realm

  • 数据库认证
@RequestMapping("/user/login")
public String login2(String username,String password){
  // 1.获取SecurityManager对象
  DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  // 2.为SecurityManager对象设置Realm
  JdbcRealm jdbcRealm = new JdbcRealm();
  jdbcRealm.setDataSource(dataSource);
  securityManager.setRealm(jdbcRealm);
  // 3.将SecurityManager对象设置到运行环境中
  SecurityUtils.setSecurityManager(securityManager);
  // 4.获取Subject对象
  Subject subject = SecurityUtils.getSubject();
  // 5.将前端传来的用户名密码封装为Shiro提供的身份对象
  UsernamePasswordToken token = new UsernamePasswordToken(username, password);
  try {
    // 6.shiro认证
    subject.login(token);
    // 7.认证通过跳转到主页面
    return "main";
   }catch (AuthenticationException e){
    // 8.认证不通过跳转到失败页面
    return "fail";
   }
}

注意: 使用JDBCRealm数据库表必须叫users,且表中必须要有username和password两列,因为JDBCRealm是通过查询users表对用户进行认证

JDBCRealm源码

将Shiro对象交给容器管理

在上述案例中,所有关于Shiro的对象都是我们自己创建的,我们在SpringBoot中使用Shiro,可以将Shiro的对象交给容器管理简化业务代码。

  • 创建Shiro配置类
@Configuration
public class ShiroConfig {
  // SecurityManager对象
  @Bean
  public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm){
    DefaultWebSecurityManager defaultSecurityManager=new DefaultWebSecurityManager();
    defaultSecurityManager.setRealm(jdbcRealm);
    return defaultSecurityManager;
   }


  // JdbcRealm
  @Bean
  public JdbcRealm getJdbcRealm(DataSource dataSource) {
    JdbcRealm jdbcRealm = new JdbcRealm();
    jdbcRealm.setDataSource(dataSource);
    return jdbcRealm;
   }
}

自定义Realm

JdbcRealm也可以帮我们完成认证的功能,但是使用JdbcRealm,数据库表名,字段名,认证逻辑都不能变,我们可以通过自定义Realm完成更灵活的认证。

喜欢阅读源码的小伙伴不难发现JdbcRealm继承了AuthorizingRealm,我们自定义Realm时也要继承AuthorizingRealm,在 AuthorizingRealm中具有授权方法,AuthorizingRealm继承了AuthenticatingRealm,AuthenticatingRealm中具有认证方法,我们只要继承了 AuthorizingRealm就可以自定义授权和认证的方法。

JdbcRealm源码

AuthorizingRealm自定义授权方法

AuthenticatingRealm自定义认证方法

package com.xiaoxiao.relam;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiaoxiao.domain.Permission;
import com.xiaoxiao.domain.Users;
import com.xiaoxiao.mapper.UsersMapper;
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.List;


public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UsersMapper usersMapper;


    // 自定义认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取用户输入的用户名
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        // 根据用户名查询用户
        QueryWrapper<Users> queryWrapper = new QueryWrapper();
        queryWrapper.eq("username",username);
        Users users = this.usersMapper.selectOne(queryWrapper);
        if(users == null){
            throw new UnknownAccountException("账户不存在");
        }
        // 将查询到的用户封装为身份信息
        /*
            参数一:用户信息
            参数二:用户密码
            参数三:盐
            参数四:Realm名
        */
        return new SimpleAuthenticationInfo(users,users.getPassword(),
                ByteSource.Util.bytes(users.getSalt()),"myRealm");
    }

    // 自定以授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("开始授权");
        // 拿到用户认证信息
        Users users = (Users) principalCollection.getPrimaryPrincipal();
        // 查询用户权限
        List<Permission> permissionList = this.usersMapper.findById(users.getUid());
        // 遍历权限对象,将所有权限交给Shiro管理
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        permissionList.forEach(permission -> {
            simpleAuthorizationInfo.addStringPermission(permission.getUrl());
        });
        return simpleAuthorizationInfo;
    }

}

多Realm认证策略

  • 在实际开发中我们的认证策略可能不止一种,此时我们就需要多Realm认证策略
package com.xiaoxiao.relam;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiaoxiao.domain.Admin;
import com.xiaoxiao.mapper.AdminMapper;
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.springframework.beans.factory.annotation.Autowired;

public class MyRealm2 extends AuthorizingRealm {

    @Autowired
    private AdminMapper adminMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        QueryWrapper<Admin> queryWrapper = new QueryWrapper();
        queryWrapper.eq("name",username);
        Admin admin = this.adminMapper.selectOne(queryWrapper);
        if(admin == null){
            throw new UnknownAccountException("账户不存在");
        }
        return new SimpleAuthenticationInfo(admin,admin.getPassword(),"myRealm2");
    }
}
  • 为SecurityManager对象设置Realm
 @Bean
    public DefaultWebSecurityManager securityManager(MyRealm myRealm,MyRealm2 myRealm2,
                                                     DefaultWebSessionManager defaultWebSessionManager,
                                                     EhCacheManager ehCacheManager){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        List<Realm> list = new ArrayList();
        list.add(myRealm);
        list.add(myRealm2);
        securityManager.setRealms(list);
        return securityManager;
    }

多Realm认证策略

如果有多个Realm,怎样才能认证成功,这就是认证策略。认证策略主要使用的是 AuthenticationStrategy接口,这个接口有三个实现类:

  • AtLeastOneSuccessfulStrategy(默认):只要有一个Realm验证成功即可,返回所有成功的认证信息
  • FirstSuccessfulStrategy:t-node=“block” data-draft-type=“table” data-size=“normal” data-row-style=“normal”>
只要有一个Realm验证成功即可,只返回第一个成功的认证信息,其他的忽略
  • AllSuccessfulStrategy:
所有Realm验证成功才算成功,如果有一个失败则认证失败
// 设置Realm认证策略
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        // 只要有一个Realm验证成功即可,返回所有成功的认证信息
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

@Bean
    public DefaultWebSecurityManager securityManager(MyRealm myRealm,MyRealm2 myRealm2,
                                                     DefaultWebSessionManager defaultWebSessionManager,
                                                     EhCacheManager ehCacheManager){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置Realm管理者(需要在realm之前)
        securityManager.setAuthenticator(modularRealmAuthenticator());
        List<Realm> list = new ArrayList();
        list.add(myRealm);
//        list.add(myRealm2);
        securityManager.setRealms(list);
        securityManager.setSessionManager(defaultWebSessionManager);
        securityManager.setCacheManager(ehCacheManager);
        return securityManager;
    }

Shiro认证_异常处理

当Shiro认证失败后,会抛出AuthorizationException异常。该异常的子类分别代表不同的认证失败原因,我们可以通过捕捉它们确定认证失败原因。

  • DisabledAccountException:账户失效
  • ConcurrentAccessException:竞争次数过多
  • ExcessiveAttemptsException:尝试次数过多
  • UnknownAccountException:用户名不正确
  • IncorrectCredentialsException:凭证(密码)不正确
  • ExpiredCredentialsException:凭证过期

SHiro认证_散列算法

散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,适合于对密码进行加密。比如密码admin,产生的散列值是21232f297a57a5a743894a0e4a801fc3,但在md5解密网站很容易的通过散列值得到密码admin。所以在加密时我们可以加一些只有系统知道的干扰数据,这些干扰数据称之为“盐”,并且可以进行多次加密,这样生成的散列值相对来说更难破解。

Shiro支持的散列算法:

Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、Sha384Hash、Sha512Hash

   @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(5);
        return hashedCredentialsMatcher;
    }
   @Bean
    public MyRealm myRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return myRealm;
    }

Shiro认证_过滤器

在以上案例中,虽然有认证功能,但即使没有登录也可以访问系统资源。如果要配置认证后才能访问资源,就需要使用过滤器拦截请求。Shiro内置了很多过滤器:

  • anon:不需要认证即可访问的资源(比如登录页、登录路径、登录失败页面)
  • authc:配置登录认证后才可访问的资源(一些安全性比较高的,比如:支付)
  • user:配置登录认证或者记住我功能即可访问的资源
    // 过滤器
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        // 创建Shiro过滤器工厂
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置SecurityManager
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        // 设置请求过滤
        Map<String,String> map = new HashMap();
        // 不需要认证就能访问的资源
        map.put("/login.html","anon");
        map.put("/user/login2","anon");
        map.put("/fail.html","anon");
        map.put("/css/**","anon");
        // 授权过滤器
        map.put("/reportform/find","perms[/reportform/find]");
        map.put("/salary/find","perms[/salary/find]");
        map.put("/staff/find","perms[/staff/find]");
        // 需要认证才能访问的资源
        map.put("/user/pay","authc");
        map.put("/**","user");
        // 为过滤器工厂设置过滤条件
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        // 设置登录页面
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        // 设置权限不足的访问的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noPermission.html");
        return shiroFilterFactoryBean;
    }

Shiro认证_记住我功能

Remember Me为“记住我”功能,即登录成功后,下次访问系统时无需重新登录。当使用“记住我”功能登录后,Shiro会在浏览器Cookie中保存序列化后的认证数据。之后浏览器访问项目时会携带该Cookie数据,这样不登录也可以完成认证。

  1. 配置Cookie生成器
  2. 配置记住我管理器
  // cookie生成器
    @Bean
    public SimpleCookie simpleCookie(){
        SimpleCookie rememberCookie = new SimpleCookie("rememberCookie");
        // cookie保存的时间 单位:s
        rememberCookie.setMaxAge(20);
        return rememberCookie;
    }

    // 记住我管理器
    @Bean
    public CookieRememberMeManager cookieRememberMeManager(SimpleCookie simpleCookie){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(simpleCookie);
        // 密钥
        cookieRememberMeManager.setCipherKey(Base64.decode("jdikowje9tru908uf9vjhfreoiqehj0g431=-1=023"));
        return cookieRememberMeManager;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿晓晓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值