前言:

本文内容:记住账号及首页定制、Shiro快速开始、Shiro的Subject分析

推荐免费SpringBoot基础教程视频:【狂神说Java】SpringBoot最新教程通俗易懂_哔哩哔哩_bilibili

记住账号及首页定制

  1. SecurityConfig添加记住账号功能

    1
    2
     // 开启记住账号功能 cookie 默认保存两周
    http.rememberMe();
  2. 运行测试

    登录时勾选记住账号,关闭浏览器再打开,账号依然是登录状态。

  3. 修改index.html中的登录地址,换为toLogin

    1
    2
    3
    <a class="item" th:href="@{/tologin}">
    <i class="address card icon"></i> 登录
    </a>
  4. SecurityConfig中关闭CSRF防护用来测试

    1
    2
    // 关闭CSRF防护 (仅为测试使用,正是开发不会关闭的)
    http.csrf().disable();
  5. SecurityConfig中指定登录页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    http
    .formLogin()
    // 指定登录页面
    .loginPage("/toLogin")
    // 登录认证url
    .loginProcessingUrl("/login")
    // 账号和密码接收
    .usernameParameter("username")
    .passwordParameter("password")
  6. login.html中添加记住账号的选项

    1
    2
    3
    4
    <!-- 记住账号-->
    <div class="field" style="text-align: center">
    <input type="checkbox" class="ui" name="remember">记住账号
    </div>
  7. 修改SecurityConfig中的记住账号

    1
    2
    // 开启记住账号功能 cookie 默认保持两周
    http.rememberMe().rememberMeParameter("remember");
  8. 运行测试,基本功能完成

Shiro快速开始

什么是Shiro?

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

有哪些功能?

Shiro

  • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。
  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如Web环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了;

Shiro架构(外部)

Shiro外部架构

  • Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;(核心)
  • Realm:域,Shiro Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;

我们可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

Shiro架构(内部)

Shiro内部架构

  • Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;
  • SecurityManager:相当于 SpringMVC 中的 DispatcherServlet,是Shiro的心脏;所有具体的交互都通过 SecurityManager 进行控制
  • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;
  • Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;
  • Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;
  • SessionManager;Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所以呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;
  • SessionDao:Dao 大家都用过,数据访问对象,用于会话的 CRUD,也可以自定义SessionDao和使用缓存
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存。
  • Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密。

官方项目快速搭建

  1. 搭建一个Maven项目分析shiro,并导入官方的quickstart

    github:shiro/samples/quickstart at main · apache/shiro (github.com)

  2. pom中一些依赖和插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <dependencies>
    <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.1</version>
    </dependency>

    <!-- configure logging -->
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.2</version>
    </dependency>
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.14.1</version>
    </dependency>
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
    </dependencies>
    <build>
    <plugins>
    <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.6.0</version>
    <executions>
    <execution>
    <goals>
    <goal>java</goal>
    </goals>
    </execution>
    </executions>
    <configuration>
    <classpathScope>test</classpathScope>
    <mainClass>Quickstart</mainClass>
    </configuration>
    </plugin>
    </plugins>
    </build>
  3. 运行测试

    1
    2
    3
    4
    5
    12:31:21.834 [main] INFO  Quickstart - Retrieved the correct value! [aValue]
    12:31:21.837 [main] INFO Quickstart - User [lonestarr] logged in successfully.
    12:31:21.837 [main] INFO Quickstart - May the Schwartz be with you!
    12:31:21.837 [main] INFO Quickstart - You may use a lightsaber ring. Use it wisely.
    12:31:21.837 [main] INFO Quickstart - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. Here are the keys - have fun!

Shiro的Subject分析

Subject分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {

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


public static void main(String[] args) {

//使用配置的
//域、用户、角色和权限使用简单的INI配置。
//我们将使用一个工厂来实现这一点,该工厂可以接收一个.ini文件并
//返回SecurityManager实例:
//使用shiro。 classpath下的ini文件
//(file:and url:前缀分别从文件和url加载):
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();

//对于这个简单的示例quickstart,使用SecurityManager
//可作为JVM单例访问。大多数应用程序不会这样做
//而是依赖于它们的容器配置或web.xml
//网络应用程序。这里不讲
SecurityUtils.setSecurityManager(securityManager);

// 现在设置了一个简单的Shiro环境,让我们看看您可以做什么

// 获取当前正在执行的用户:
Subject currentUser = SecurityUtils.getSubject();

// 用会话做一些事情(不需要web或EJB容器!!!)
// 获取session 并通过setAttribut传递一些值
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
// 获取session中的值存放在value 并判断是否与给session传递的值相同
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}

// 让我们登录当前用户,以便检查角色和权限::
if (!currentUser.isAuthenticated()) {
// 通过账号和密码创建一个Token Token:令牌
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 记住我
token.setRememberMe(true);
try {
// 执行login方法,通过token
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//异常处理
}
}

//打印用户名:
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

// 测试一个角色
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}

//测试类型化权限(非实例级)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

//实例级权限((very powerful)
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

//注销
currentUser.logout();
// 结束
System.exit(0);
}
}

核心的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取当前正在执行的用户:
Subject currentUser = SecurityUtils.getSubject();
// 获取session 并传递一些值
Session session = currentUser.getSession();
// 检查用户权限
currentUser.isAuthenticated()
// 未知权限
UnknownAccountException uae
// 账号或密码不正确
IncorrectCredentialsException ice
// 锁定的权限
LockedAccountException lae
// 获取角色
currentUser.hasRole("schwartz")
//注销
currentUser.logout();