apache-shiro

shiro核心类

image-20201003202330467

一、第一个shiro程序

pom.xml

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
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>spring-shrio</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.6.0</version>
</dependency>



<!-- configure logging -->
<!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.0-alpha1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.0-alpha1</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>

</project>

image-20201003190146912

文件需要的目录;

实际上就是按照官方的指示,伪造一个quick start来帮助我们运行程序

二、分析QuickStart

原码:

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
package com.saxon.shrio;

public class Quickstart {

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


public static void main(String[] args) {

Factory<SecurityManager> factory = new IniSecurityManagerFactory ("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

// Now that a simple Shiro environment is set up, let's see what you can do:

// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();

// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}

// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
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) {
//unexpected condition? error?
}
}

//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}

//test a typed permission (not instance-level)
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.");
}

//a (very powerful) Instance Level permission:
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!");
}

//all done - log out!
currentUser.logout();

System.exit(0);
}
}

官方文档:

Listing 1. Acquiring the Subject

1
2
3
4
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();

Listing 2. Configuring Shiro with INI

1
2
3
4
5
6
[main]
cm = org.apache.shiro.authc.credential.HashedCredentialsMatcher
cm.hashAlgorithm = SHA-512
cm.hashIterations = 1024
# Base64 encoding (less text):
cm.storedCredentialsHexEncoded = false

Listing 3. Loading shiro.ini Configuration File

1
2
3
4
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.Factory;

Listing 4. Example realm configuration snippet to connect to LDAP user data store

1
2
3
4
5
[main]
ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
ldapRealm.contextFactory.url = ldap://ldapHost:389
ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5

Listing 5. Subject Login

1
2
3
//1. Acquire submitted principals and credentials:
AuthenticationToken token =
new UsernamePasswordToken(username, password);

Listing 6. Handle Failed Login

1
2
3
4
5
6
7
8
9
//3. Login:
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ice) { …
} catch (LockedAccountException lae) { …
}

catch (AuthenticationException ae) {…
}

Listing 7. Role Check

1
2
3
4
5
if ( subject.hasRole(“administrator”) ) {
//show the ‘Create User’ button
} else {
//grey-out the button?
}

Listing 8. Permission Check

1
2
3
4
5
if ( subject.isPermitted(“user:create”) ) {
//show the ‘Create User’ button
} else {
//grey-out the button?
}

Listing 9. Instance-Level Permission Check

1
2
3
4
5
if ( subject.isPermitted(“user:delete:jsmith”) ) {
//delete the ‘jsmith’ user
} else {
//don’t delete ‘jsmith’
}

Listing 10. Subject’s Session

1
2
Session session = subject.getSession();
Session session = subject.getSession(boolean create);

Listing 11. Session methods

1
Session session = subject.getSession();

上面的实体类大部分就是按照这个来书写的,但是顺序不一定一样;

三、三大core概念

一、Subject(当前处理对象)

Once you acquire the Subject, you immediately have access to 90% of everything you’d want to do with Shiro for the current user, such as login, logout, access their session, execute authorization checks, and more - but more on this later. The key point here is that Shiro’s API is largely intuitive because it reflects the natural tendency for developers to think in ‘per-user’ security control. It is also easy to access a Subject anywhere in code, allowing security operations to occur anywhere they are needed.

获得Subject的方法:

1
Subject currentUser = SecurityUtils.getSubject();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean ();
shiroFilterFactoryBean.setSecurityManager (defaultWebSecurityManager);
//设置权限登录
Map<String, String> filterMap=new IdentityHashMap<> ();
filterMap.put ("/test/1","port[8080]");
filterMap.put ("/test/1","perms[user:add]");

shiroFilterFactoryBean.setFilterChainDefinitionMap (filterMap);
//登录请求
shiroFilterFactoryBean.setLoginUrl ("/tologin");

shiroFilterFactoryBean.setUnauthorizedUrl ("");
return shiroFilterFactoryBean;
}

就是可以对我们当前要进行操作或者请求的处理,比如配置拦截器等;

authentication token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping ("/login")
public String login (String username, String password, Model model) {
Subject subject = SecurityUtils.getSubject ();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken (username, password);
try {
subject.login (usernamePasswordToken);
return "/index";
} catch (UnknownAccountException e) {
model.addAttribute ("msg", "用户错误");
return "/login";
} catch (IncorrectCredentialsException e) {
model.addAttribute ("msg", "密码错误");
return "/login";
}
}
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
package com.saxon.shiro.config;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) {
return null;
}

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException {
String username = "root";
String password = "123456";
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if (!token.getUsername ().equals (username) ) {
return null;
}
return new SimpleAuthenticationInfo ("", password, "");

}
}

使用realm对象;对用户进行一个认证;

会对你的密码和账户验证;

二、Realm

Realm:域,Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。从这个意义上讲,Realm 实质上是一个安全相关的 DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro 。当配置 Shiro时,你必须至少指定一个 Realm ,用于认证和(或)授权。配置多个 Realm 是可以的,但是至少需要一个。
Shiro 内置了可以连接大量安全数据源(又名目录)的 Realm,如 LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等。如果缺省的 Realm 不能满足需求,你还可以插入代表自定义数据源的自己的 Realm 实现。

Realm能做的工作主要有以下几个方面:

  • 身份验证(getAuthenticationInfo 方法)验证账户和密码,并返回相关信息

  • 权限获取(getAuthorizationInfo 方法) 获取指定身份的权限,并返回相关信息

  • 令牌支持(supports方法)判断该令牌(Token)是否被支持

    令牌有很多种类型,例如:HostAuthenticationToken(主机验证令牌),UsernamePasswordToken(账户密码验证令牌)

这里主来说明一下关于前两点验证方面的逻辑,因为令牌一般用的都是 UsernamePasswordToken,哪怕用 HostAuthenticationToken,也没必要细讲,这个函数很少用到。

例子:

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
public class MyRealm extends AuthorizingRealm {
@Autowired
TeacherService teacherService;

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo ();
Subject subject = SecurityUtils.getSubject ();
Teacher teacher = (Teacher) subject.getPrincipal ();
simpleAuthorizationInfo.addStringPermission (teacher.getPerm ());
return simpleAuthorizationInfo;
}

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;//从数据库里面获得数据
Teacher teacher = teacherService.getTeacherByName (token.getUsername ());
if (teacher == null) {
return null;
}
return new SimpleAuthenticationInfo (teacher, teacher.getPassword (), "");

}
}

顺序一般是先认证后再当你需要访问我们需要权限的页面或者请求时,就会授权;

SimpleAuthenticationInfo(认证):

1
2
3
4
5
6
7
8
9
/**
* Simple implementation of the {@link org.apache.shiro.authc.MergableAuthenticationInfo} interface that holds the principals and
* credentials.
*
* @see org.apache.shiro.realm.AuthenticatingRealm
* @since 0.9
*/
public class SimpleAuthenticationInfo implements MergableAuthenticationInfo, SaltedAuthenticationInfo {

*{@link的简单实现org.apache.shiro.认证信息}包含主体和凭证。主体就是用户,凭证就是用户密码等,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Constructor that takes in a single 'primary' principal of the account and its corresponding credentials,
* associated with the specified realm.
* <p/>
* This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based
* on the {@code principal} and {@code realmName} argument.
*
* @param principal the 'primary' principal associated with the specified realm.
* @param credentials the credentials that verify the given principal.
* @param realmName the realm from where the principal and credentials were acquired.
*/
public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {
this.principals = new SimplePrincipalCollection(principal, realmName);
this.credentials = credentials;
}

第二个参数就是对第一个参数的一个验证,而验证方法就是将第二个参数的值与令牌里面的密码进行匹配;

SimpleAuthorizationInfo(授权)

1
2
3
4
5
6
7
8
/**
* Simple POJO implementation of the {@link AuthorizationInfo} interface that stores roles and permissions as internal
* attributes.
*
* @see org.apache.shiro.realm.AuthorizingRealm
* @since 0.9
*/
public class SimpleAuthorizationInfo implements AuthorizationInfo {

AuthorizationInfo 用于聚合授权信息的:给用户授权;

例子:

1
2
3
4
5
6
7
8
9
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo ();
Subject subject = SecurityUtils.getSubject ();
Teacher teacher = (Teacher) subject.getPrincipal ();
simpleAuthorizationInfo.addStringPermission (teacher.getPerm ());
return simpleAuthorizationInfo;
}

三、securitymanager

1
2
3
4
5
6
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager (@Qualifier ("myRealm") MyRealm myRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager ();
defaultWebSecurityManager.setRealm (myRealm);
return defaultWebSecurityManager;
}

从我现在的角度来看就是把realm和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
package com.saxon.shiro.config;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
/*shiro 三大对象
* 1.Subject =============>ShiroFilterFactoryBean
* 2.SecurityManger==============>DefaultWebSecurityManager
* 3.realm====================>extends AuthorizingRealm
* 获得顺序相反
* @Qualifier指定的使方法名,可以通过@Bean(name="")来改变方法名注入
* */

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean ();
shiroFilterFactoryBean.setSecurityManager (defaultWebSecurityManager);
//设置权限登录
Map<String, String> filterMap=new IdentityHashMap<> ();
filterMap.put ("/test/1","port[8080]");
filterMap.put ("/test/1","perms[user:add]");

shiroFilterFactoryBean.setFilterChainDefinitionMap (filterMap);
shiroFilterFactoryBean.setLoginUrl ("/tologin");

shiroFilterFactoryBean.setUnauthorizedUrl ("");
return shiroFilterFactoryBean;
}


@Bean
public DefaultWebSecurityManager defaultWebSecurityManager (@Qualifier ("myRealm") MyRealm myRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager ();
defaultWebSecurityManager.setRealm (myRealm);
return defaultWebSecurityManager;
}

@Bean
public MyRealm myRealm () {
return new MyRealm ();
}
}

书写的顺序从下到上,因为前面的需要后面的对象;

默认的shiro过滤器:

image-20201004204321990

简写(加粗为常用) 名称 优先级(1为最高) 说明 对应Java类
anon 匿名拦截器 1 不需要登录就能访问,一般用于静态资源,或者移动端接口 org.apache.shiro.web.filter.authc.AnonymousFilter
authc 登录拦截器 2 需要登录认证才能访问的资源 org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic Http拦截器 3 Http身份验证拦截器,非常用类型,不太了解 org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout 登出拦截器 4 用户登出拦截器,主要属性:redirectURL退出登录后重定向的地址 org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation 不创建会话拦截器 5 调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常 org.apache.shiro.web.filter.authc.NoSessionCreationFilter
prems 权限拦截器 6 验证用户是否拥有资源权限 org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter
port 端口拦截器 7 其主要属性: port(80) 如果用户访问该页面是非 80,将自动将请求端口改为 80 并重定向到该 80 端口 org.apache.shiro.web.filter.authc.PortFilter
rest rest风格拦截器 8 rest 风格拦截器,自动根据请求方法构建权限字符串构建权限字符串;非常用类型拦截器 org.apache.shiro.web.filter.authc.HttpMethodPermissionFilter
roles 角色拦截器 9 验证用户是否拥有资源角色 org.apache.shiro.web.filter.authc.RolesAuthorizationFilter
ssl SSL拦截器 10 只有请求协议是https才能通过,否则你会自动跳转到https端口(443) org.apache.shiro.web.filter.authc.SslFilter
user 用户拦截器 11 用户拦截器,用户已经身份验证 / 记住我登录的都可; org.apache.shiro.web.filter.authc.UserFilter

使用方法:

使用一个map集合指定过滤器;

1
2
shiroFilterFactoryBean.setFilterChainDefinitionMap (filterMap); 
Map<String, String> filterMap=new IdentityHashMap<> ();

至于map选用问题,可以试着选,我选这个的目的就是为了让同一个请求可以有多个过滤器;例如:

1
2
filterMap.put ("/test/1","port[8080]");
filterMap.put ("/test/1","perms[user:add]");

至于使用的方法就是

  • **key:**就是你要请求过滤的路径
  • value:就是你的属性值,没有这个属性的不可以访问;

后面授权的时候,就根据这个来选择;

例如:我们选用的是perms

1
2
3
4
5
6
7
8
9
10
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo ();
Subject subject = SecurityUtils.getSubject ();
Teacher teacher = (Teacher) subject.getPrincipal ();
//主要语句
simpleAuthorizationInfo.addStringPermission (teacher.getPerm ());
return simpleAuthorizationInfo;
}