官方教程

参考


  • Apache Shiro 是 Java 的一个安全(权限)框架。
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以在 JavaSE 环境,也可以用在 JavaEE 环境
  • Shiro 可以完成:认证、授权、加密、会话管理、与Web集成、缓存等。
  • 下载:http://shiro.apache.org/

功能简介

四个主要功能

  • Authentication:认证
  • Authorization:授权
  • Session Manager:会话管理
  • Cryptography:加密


Shiro 架构

  • Subject: 一个“门面”,在 shiro 内部,可以视作 “用户”
  • SecurityManager: 核心
  • Realm:类似“DAO”。





HelloWorld

参考:官方教程 - Your First Apache Shiro Application

<mark>创建 Maven 项目</mark>

依赖

<dependencies>
 <dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-core</artifactId>
     <version>1.4.1</version>
 </dependency>
 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-simple</artifactId>
     <version>1.6.4</version>
 </dependency>
 <dependency>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
     <version>1.2</version>
 </dependency>
</dependencies>

Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

    public static void main(String[] args) {

        log.info("My First Apache Shiro Application");

        // 用ini文件,通过factory获取securityManager
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        // 获取当前的 Subject
        Subject currentUser =  SecurityUtils.getSubject();

        // 测试使用 session
        // 1.获取 session
        Session session = currentUser.getSession();
        // 2.往 session 中放值
        session.setAttribute("someKey", "aValue");
        // 2.从 session 中取值
        Object value = session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("----> Retrieved the correct Value! [{}]", value);
        }

        // 测试当前的用户是否已经被认证,即是否已经登录
        // 调用 Subject 的 isAuthenticated
        if (!currentUser.isAuthenticated()) {
            // 把用户名和密码封装为 UsernamePasswordToken 对象
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            // rememberMe
            token.setRememberMe(true);
            try {
                // 执行登录
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("账号 {} 不存在", token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("账号 {} 密码错误", token.getPrincipal());
            } catch (LockedAccountException lae) {
                log.info("账号 {} 被锁定", token.getPrincipal());
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?)
            catch (AuthenticationException ae) {
                // 所有认证时异常的父类
                // unexpected condition? error?
            }
        }

        // 告知登录成功
        log.info("----> User [{}] logged in successfully.", currentUser.getPrincipal());

        // 测试是否有某一个角色,调用 Subject 的 hasRole 方法。
        if(currentUser.hasRole("schwartz")) {
            log.info("----> May the Schwartz be with you!");
        }else {
            log.info("----> Hello, mere mortal.");
        }

        // 测试用户是否有某个行为,调用 Subject 的 isPermitted 方法
        if(currentUser.isPermitted("lightsaber:weild")) {
            log.info("You may use a lightsaber ring. Use it wisely.");
        }else{
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        // 测试用户是否具备某一个行为。(相对上面的,这个更具体)
        if(currentUser.isPermitted("user:delete:zhangsan")) {
            log.info("可以在“用户”中“删除”“张珊”");
        }else{
            log.info("Sorry, 你不允许删除我心爱的张珊");
        }

        // 执行登出,调用 Subject 的 Logout() 方法
        currentUser.logout();

        if(currentUser.isAuthenticated()) {
            log.info("登出失败");
        }else{
            log.info("登出成功");
        }

        System.exit(0);
    }
}

shiro.ini

# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
# 用户:行为:id ==> goodguy“角色”可以在“用户”中“删除”“张珊”
goodguy = user:delete:zhangsan



集成 SpringBoot

github - https://github.com/LawssssCat/shiro-template

需要什么依赖,官网说很清楚了:http://shiro.apache.org/download.html

<mark>创建SpringBoot项目</mark>
<mark>pom.xml</mark>

<?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.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-shiro-spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-shiro-spring</name>
    <description>Demo project for Spring Boot</description>

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

    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.5.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

shiro 配置类

package com.example.demoshirospring.config;

import jdk.nashorn.internal.parser.Token;
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.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
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;

import java.util.Map;

@Configuration
public class ShiroConfig {

    // 1.配置 SecurityManager(关键)
    @Bean("securityManager")
    @Autowired
    public SecurityManager securityManager(
            @Qualifier("cacheManager") CacheManager cacheManager,
            @Qualifier("realm") Realm realm
    ) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setCacheManager(cacheManager);
        manager.setRealm(realm);
        return manager;
    }

    // 2.配置 CacheManger.
    @Bean("cacheManager")
    public CacheManager cacheManager() {
        EhCacheManager manager = new EhCacheManager();
        return manager;
    }

    // 3.配置Realm(关键)
    @Bean("realm")
    public Realm realm() {
        // 直接实现了 Realm 接口的 bean
        // 实际开发中,要么有现成的,要么有必要单独一个文档实现接口
        return new AuthorizingRealm() {
            @Override
            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
                // 暂时不处理
                return null;
            }

            @Override
            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
                // 密码为 pw 即可通过认证
                return new SimpleAuthenticationInfo(new Object(), "pw", "realName");
            }

        };
    }

    // 4.配置 Shiro 生命周期处理器.
    // 用于在实现了 Initializable 接口的 Shiro bean 初始化时调用 Initializable 接口回调(例如:CasRealm)
    // 在实现了 Destroyable 接口的 Shiro bean 销毁时调用 Destroyable 接口回调(例如:DefaultSecurityManager)
    // 可以自动的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    // 5.启用 IOC 容器中使用 shiro 的注解.
    // 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用.
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }

    // 6.配置 ShiroFilter(关键)
    // bean 名必须和下面 DelegatingFilterProxy 构造时传入的 beanName 一致
    @Bean("shiroFilter")
    @Autowired
    public ShiroFilterFactoryBean shiroFilterFactoryBean(
            @Qualifier("securityManager") SecurityManager securityManager
    ) {
        ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean();
        factory.setSecurityManager(securityManager);

        // 检测到用户未登录后,转跳的页面(通常为登录页URL)
        factory.setLoginUrl("/index");
        // 验证成功的后的默认转跳路径(若有指定路径,则不走此默认路径)
        factory.setSuccessUrl("/index");
        // 没有访问权限的转跳路径
        factory.setUnauthorizedUrl("/unauthorizedUrl");

        // 配置访问这些页面的权限
        // (本质上,key是URL,value是Filter链)
        /* anon 可以被匿名访问 authc 必须认证后,才可以访问的页面 user 必须荣有 记住我 功能才能用 perms 拥有对某个资源的权限才能访问 roles 荣有某个角色权限才能访问 ... 以上定义的枚举类为:org.apache.shiro.web.filter.mgt.DefaultFilter */
        Map<String, String> map = factory.getFilterChainDefinitionMap();
        // 必须加 "/" 斜杠
        map.put("/favicon.ico", "anon");
        map.put("/login", "anon");
        map.put("/index", "anon");
        // 表示该 uri 需要认证用户拥有 admin 角色才能访问(测试 setUnauthorizedUrl 拦截)
        map.put("/user", "roles[user]");
        map.put("/logined", "authc");
        map.put("/**", "authc");
        return factory;
    }

    // 7.注册自定义Filter (扩展)
 /* @Bean @Autowired public FilterRegistrationBean<DelegatingFilterProxy> logoutFilterRegistration( @Qualifier("securityManager") SecurityManager securityManager ) { LogoutFilter filter = new LogoutFilter(); // 登出后转跳的 URL filter.setRedirectUrl("/index"); // 把 servlet 容器中的 filter 同 spring 容器中的 bean 关联起来 FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>(); DelegatingFilterProxy proxy= new DelegatingFilterProxy(filter); registration.setFilter(proxy); // 注册的拦截 拦截的URL registration.addUrlPatterns("/logout"); registration.setOrder(1); return registration; }*/

}

Controller

package com.example.demoshirospring.controller;

import javafx.css.SimpleStyleableStringProperty;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ShiroController {

    @RequestMapping("/login")
    public String login() {
        AuthenticationToken token = new UsernamePasswordToken(
                "a"
                , "pw");
        SecurityUtils.getSubject().login(token);
        return "login";
    }

    @RequestMapping("/index")
    public String index() {
        Subject subject = SecurityUtils.getSubject();
        return "首页,是否登录:" + subject.isAuthenticated();
    }

    @RequestMapping("/unauthorizedUrl")
    public String unauthorizedUrl() {
        return "unauthorizedUrl";
    }

    @RequestMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "logout ok!";
    }

    //需要 user 用户权限
    @RequestMapping("/user")
    public String list() {
        return "user role ";
    }

    //需要 登录才可访问
    @RequestMapping("/logined")
    public String getA() {
        return "has login";
    }

}

测试

登录前

登录后

github - https://github.com/LawssssCat/shiro-template