Shiro 简单入门

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

三个核心组件:Subject, SecurityManager 和 Realms.

Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。

Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

第一步创建Springboot项目

通过自己熟悉的工具,创建一个Springboot项目

pom.xml

`<?xml version=“1.0” encoding=“UTF-8”?> <project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <groupId>com.zdltech.demo</groupId> <artifactId>shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <name>shiro</name> <description>shiro demo</description>

&lt;properties&gt;
    &lt;java.version&gt;1.8&lt;/java.version&gt;
&lt;/properties&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;

    &lt;!--shiro 依赖添加--&gt;
    &lt;!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
        &lt;artifactId&gt;shiro-spring&lt;/artifactId&gt;
        &lt;version&gt;1.4.1&lt;/version&gt;
    &lt;/dependency&gt;

&lt;/dependencies&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

</project> `


### 配置Shiro {#toc_2}

> 创建一个ShiroConfig进行配置
> 
> ```
`import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        logger.info("ShiroConfig initalized");
        // 初始化ShiroFilterFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //必须设置SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //拦截器
        Map&lt;String,String&gt; filterChainDefinitionMap = new LinkedHashMap&lt;&gt;();
        //退出
        filterChainDefinitionMap.put("/logout","logout");
        // 过滤链定义,从上向下顺序执行,一般将/** 放到最下面
        filterChainDefinitionMap.put("/admin/**","authc");
        filterChainDefinitionMap.put("/**","anon");

        // 如果不设置默认会自动寻找Web工程更目录下的“login.jsp”页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的连接
        shiroFilterFactoryBean.setSuccessUrl("/success");
        // 未授权界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(){
        System.out.println("securityManager is run");
        SecurityManager securityManager = new DefaultWebSecurityManager();
         ((DefaultWebSecurityManager) securityManager).setRealm(new MyRealm());
        return securityManager;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        System.out.println("authorizationAttributeSourceAdvisor is run");
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        System.out.println("defaultAdvisorAutoProxyCreator is run");
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
         defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return  defaultAdvisorAutoProxyCreator;
    }
}
`

`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.slf4j.Logger; import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;

public class MyRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(MyRealm.class); @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。 //认证 并且获取角色及权限 logger.info(”#################执行Shiro权限认证#######################”); String userName = (String) principalCollection.getPrimaryPrincipal(); if (userName.equals(“admin”)){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission(“admin:”); info.addStringPermission(“index:”); info.addRole(“admin”); return info; }else if (userName != null&& userName.length()>0){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission(“index:*”); info.addRole(“index”); return info; }else{ return null; }

}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    logger.info("#################执行doGetAuthenticationInfo认证#######################");
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    logger.info("user==&gt;"+token.getUsername());
    if ("admin".equals(token.getUsername())){
        return new SimpleAuthenticationInfo(token.getUsername(),"123456",token.getUsername());
    }else if (token.getUsername() != null&& token.getUsername().length()&gt;0){
        return new SimpleAuthenticationInfo(token.getUsername(),"111111",token.getUsername());
    }else{
        return null;
    }

}

@PostConstruct
public void initCredentialsMatcher(){
    setCredentialsMatcher(new CredentialsMatcher());
}

} `

> 
> ```
`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.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CredentialsMatcher extends SimpleCredentialsMatcher {
    private Logger logger = LoggerFactory.getLogger(CredentialsMatcher.class);

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        System.out.println("doCredentialsMatch is run!!!");
        UsernamePasswordToken authToken = (UsernamePasswordToken) token;
        if ("admin".equals(authToken.getUsername())){
            return  getCredentials(info).equals("123456");
        }else if( authToken.getUsername()!=null && authToken.getUsername().length()&gt;0){
            return  getCredentials(info).equals("111111");
        }else {
            return false;
        }

    }
}
`

通过这些我们配置完成Shiro

测试Shiro

最简单的基础的Shiro配置完成,下面我们来创建一个Controller测试下

`import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;

@Controller @RequestMapping(”") public class IndexController {

@GetMapping
@ResponseBody
public String defaultMethod(){

    return "ok";
}

@GetMapping("index")
@ResponseBody
public String index(){

    return "index is run ! please continue";
}

@GetMapping("login")
public String login(){
    return "login";
}

@GetMapping("admin")
@ResponseBody
public String adminindex(){
    System.out.println("admin IS run !");
    Subject subject = SecurityUtils.getSubject();
    subject.checkPermission("admin:*");
    boolean has = subject.isPermitted("admin:*");
    System.out.println("是否有权限:"+has);
    return "admin index is in";
}

@GetMapping("loginrequest")
@ResponseBody
public String loginRequest(String userName,String password){
    System.out.println("loginrequest IS run !");
    if (userName.equals("admin")&&password.equals("123456")){
        UsernamePasswordToken token =new UsernamePasswordToken();
        token.setUsername(userName);
        token.setPassword(password.toCharArray());
        System.out.println("登录成功!!!");
        Subject subject = SecurityUtils.getSubject();
        subject.login(token);
    }else if (userName!=null&&userName.length()&gt;0){
        UsernamePasswordToken token =new UsernamePasswordToken();
        token.setUsername(userName);
        token.setPassword(password.toCharArray());
        Subject subject = SecurityUtils.getSubject();
        subject.login(token);
    }else{
        System.out.println("登录失败!!!");
    }
    return " index is in";
}

} `

> 
> 对应的login.html为
> 
> ```
`&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;登录&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;form action="/loginrequest"&gt;
       用户名: &lt;input name="userName"/&gt;&lt;br&gt;
        密码:&lt;input name="password"/&gt;
        &lt;button type="submit"&gt;提交&lt;/button&gt;
  &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
`

SpringBoot 集成Shiro实现前后端分离

shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

public class MySessionManager extends DefaultWebSessionManager {

private static final String AUTHORIZATION = “Authorization”;

private static final String REFERENCED_SESSION_ID_SOURCE = “Stateless request”;

public MySessionManager() {
super();
}

@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}

使用自定义的SessionManager,在Shiro的config中

@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
return securityManager;
}

//自定义sessionManager
@Bean
public SessionManager sessionManager() {
    MySessionManager mySessionManager = new MySessionManager();
    mySessionManager.setSessionDAO(redisSessionDAO());//此处是使用redis中的Session
    return mySessionManager;
}

参考

在前后端分离的SpringBoot项目中集成Shiro权限框架

https://blog.csdn.net/u013615903/article/details/78781166