技术联盟

Shiro 简单入门

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>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--shiro 依赖添加-->
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置Shiro

创建一个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<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        //退出
        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==>"+token.getUsername());
        if ("admin".equals(token.getUsername())){
            return new SimpleAuthenticationInfo(token.getUsername(),"123456",token.getUsername());
        }else if (token.getUsername() != null&& token.getUsername().length()>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()>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()>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为

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
  <form action="/loginrequest">
       用户名: <input name="userName"/><br>
        密码:<input name="password"/>
        <button type="submit">提交</button>
  </form>
</body>
</html>

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

Share this:

码字很辛苦,转载请注明来自技术联盟《Shiro 简单入门》

评论