Bootstrap

shiro 用法

最近在做项目的时候需要用到shiro做认证和授权来管理资源

在网上看了很多文章,发现大多数都是把官方文档的简介摘抄一段,然后就开始贴代码,告诉你怎么怎么做,怎么怎么做

相信很多小伙伴即使是跟着那些示例代码做完配完,并且成功搭建,估计也是一头雾水,可能会不理解,为什么要这么做

本人也是在看了大量文章之后,然后又自己动手搭了一便,得出如下使用的感悟,特此分享给大家

 

依照程序,我要在这里对shiro做一些简介,以下内容翻译与官网首页

Apache shiro是一个功能强大、易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。有了Shiro易于理解的API,您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的Web和企业应用程序。

其次要和相关类似技术进行一个对比

在spring大家族中,安全框架也是肯定有的,那就是spring-security,至于我们为什么要用shiro而不用spring-security大家可以自行搜索下相关内容我们在此就不过多阐述了

 

ok,废话少说,我们直奔主题

可能有些小伙伴在用shiro这种框架的时候会有这种疑惑,我们为什么要用这样的框架

首先我们开发者在做一个项目的时候,对资源的权限控制和划分应该是必不可少的一部分,即哪些资源哪些角色可以访问,哪些资源哪些角色不可以访问

那么试想下这种情况,如果没有shiro这种框架,你要实现对资源的权限控制,可能会有如下几点

1,首先你要设计角色

2,为资源分配角色,并且统一注册进配置文件中

4,给用户授权

3,每当有新的请求进来时你要验证当前用户的身份,然后遍历资源权限的配置文件,查明当前用户的角色是否有资格访问

第1,2步体现在设计层面,第3步要周而复始每当有新的请求的时候都要做一遍的事情,这一步可以使用spring提供的aop机制,配置成前置通知

综上所述是不是感觉很麻烦

那么shiro呢,就为我们简化了上述步骤,使我们能以一种较为简洁的方式,实现对项目资源的权限控制

同时shiro还集成了很多加密机制(MD5,base64,SHA)等,可以直接以调用的方式对我们的密码或者报文进行加密措施非常的方便

 

首先我门引入pom依赖

 

<!--  shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

 

集成shiro非常的简单,我门只需要自定义一下我们的认证和授权然后配置以下过滤器即可

 

认证和授权

复制代码

import com.sc.starry_sky.entity.PageData;
import com.sc.starry_sky.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

@Component("MyShiroRealm")
public class MyShiroRealm extends AuthorizingRealm{

    //用于用户查询
    @Autowired
    private UserService userService;

    //添加角色权限
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户
        PageData user = (PageData) principalCollection.getPrimaryPrincipal();
        //获取权限列表
        ArrayList<PageData> roles = (ArrayList<PageData>) user.get("roles");
        //添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        roles.forEach(item -> simpleAuthorizationInfo.addRole(item.getString("roleName")));
        return simpleAuthorizationInfo;
    }

    //登录认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;

        String username = usernamePasswordToken.getUsername();
        
        PageData user = userService.findUserByUsername(username);
        if (user == null) {
            return null;
        } else {
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getString("password"), getName());
            return simpleAuthenticationInfo;
        }
    }

}

复制代码

 

自定义密码的校验规则

复制代码

import com.sc.starry_sky.util.PasswordUtil;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import com.sc.starry_sky.entity.PageData;
import org.springframework.stereotype.Component;

@Component("CredentialMatcher")
public class CredentialMatcher extends SimpleCredentialsMatcher{
    
    @Autowired
    PasswordUtil passwordUtil;
    
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        PageData user = (PageData)info.getPrincipals().getPrimaryPrincipal(); //这里取出的是我们在认证方法中放入的用户信息也就是我们从数据库查询出的用户信息
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;//这里是前端发送的用户名密码信息
        String requestPwd = new String(usernamePasswordToken.getPassword());
        String requestPwdEncryption = passwordUtil.getEncryptionPassword(requestPwd, user.getString("userId"));//获取加密后的密码

        String dbPwd = user.getString("password");
        return requestPwdEncryption.equals(dbPwd);
    }
    

}

复制代码

 

密码加密工具类

复制代码

import org.apache.shiro.crypto.hash.SimpleHash;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Random;

@Component
public class PasswordUtil {
    
    @Value("${password.algorithmName}") //这个可以在application.properites里自行配置 如(md5,sha,base64)等等
    private String algorithmName;
    @Value("${password.hashIterations}")//这个是加密的次数
    private int hashIterations;
    
    /**
     * 返回加密后的密码
     * @param password
     * @param userId
     * @return
     */
    public String getEncryptionPassword(String password , String userId){
      //四个参数的讲解 1,加密类型,2,原始密码,3,盐值,可以是固定的,这里我门采用用户的userId作为用户的盐值进行加盐,4,加密的迭代次数
        return new SimpleHash(algorithmName, password, userId, hashIterations).toString();
    }
}

复制代码

 

配置过滤器

复制代码

import java.util.HashMap;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

    @Autowired
    MyShiroRealm myShiroRealm;
    @Autowired
    CredentialMatcher CredentialMatcher;

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);

        filterFactoryBean.setLoginUrl("/loginPage");

        // 成功登陆后的界面
        //filterFactoryBean.setSuccessUrl("/indexPage");

        // 没有权限访问的界面
        //filterFactoryBean.setUnauthorizedUrl("/unauthorized");

        HashMap<String, String> filterMap = new HashMap<>();

        // 配置不会被拦截的链接 顺序判断
        filterMap.put("/static/**", "anon");
        filterMap.put("/doLogin", "anon");
        filterMap.put("/loginPage", "anon");
        filterMap.put("/SubmitRegistInformation", "anon");
        filterMap.put("/SendMessageCode", "anon");
        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterMap.put("/logout", "logout");

        // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterMap.put("/**", "authc");
        // 所有的页面都需要user这个角色
        filterMap.put("/**", "roles[user]");

        filterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return filterFactoryBean;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm authRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    @Bean("myShiroRealm")
    public MyShiroRealm myShiroRealm() {
        //设置缓存
        myShiroRealm.setCacheManager(new MemoryConstrainedCacheManager());
        //设置密码校验规则
        myShiroRealm.setCredentialsMatcher(CredentialMatcher);
        return myShiroRealm;
    }

}

复制代码

 

登录的controller方法

复制代码

@Controller
public class LoginRegistController extends BaseController {

    @Autowired
    PasswordUtil passwordUtil;

    @Autowired
    UserService userService;

    @PostMapping("/doLogin")
    @ResponseBody
    public Map<String,Object> doLogin(){
        HashMap<String, Object> resultMap = new HashMap<String, Object>();
        try{
            PageData user = this.getPageData();
            UsernamePasswordToken token = new UsernamePasswordToken(user.getString("username"), user.getString("password"));
            SecurityUtils.getSubject().login(token);
            PageData db_user = (PageData) SecurityUtils.getSubject().getPrincipal();//登录成功之后,取出用户信息放进session存储域
            this.getRequest().getSession().setAttribute("user_information",db_user);
            resultMap.put("status", 200);
            resultMap.put("message", "登录成功");
            return resultMap;
        }catch(Exception e){
            resultMap.put("status", 500);
            resultMap.put("message", e.getMessage());
            return resultMap;
        }
    }


    @GetMapping("/loginPage")
    public ModelAndView loginPage(String name){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("login_regist");
        return mv;
    }
}
;