学习 准备 尝试 谨慎小心

0%

准备:

  • 公众号历史消息的请求地址
  • cookie

2种方法获取地址和cookie

  1. 用浏览器打开公众号历史消息页面,使用开发工具获取请求地址和cookie
  2. 用抓包工具获取请求地址和cookie,比如 Fidder、charles

源码如下:

1
# -*- coding: utf-8 -*-
2
import requests
3
import jsonpath
4
import json
5
 
6
headers = {
7
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
8
    "Host": "mp.weixin.qq.com",
9
    "Referer": "https://mp.weixin.qq.com",
10
	# 设置好cookie
11
    "Cookie": "RK=c+zMAuktP8; ptcz=005f33a36542502454b119382853de0d9ea6aa693367c6ae312a1c34c0dcfebe; pgv_pvi=4737076224; ptui_loginuin=1254428526; pgv_pvid=3106019708; wxuin=1411706915; devicetype=Windows7; version=62070152; lang=zh_CN; pass_ticket=FyE/xFBG3nyqQokgb6OoN9VFXaZVJPK53op9NWOsmqB2HZm8CUhy5Hz9+fgVo+PA; wap_sid2=CKPgk6EFElxuY2VlWndUVWJjT1d2YnVzcXMxTk4xcldfQ3hVQUYzUnB1LTVZTDlyQkVCb2ZPZHQ2S3hXbUdsMEJ0VkNVdDBZUmJXUC0wb0ZUb1N6U0JVSlYybHdGZ29FQUFBfjCJsKjuBTgNQJVO"
12
           }
13
 
14
for i in range(10):
15
    # 设置请求地址
16
	url = "https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=MjM5Mjg3MTIzMQ==&f=json&offset={}&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=&wxtoken=&appmsg_token=1034_N22Qb3TiIEjqcdGLa-1KO9dkAZgO1e2zBcGB5w~~&x5=0&f=json".format(str(i * 10))
17
	
18
	response = requests.get(url, headers = headers)
19
 
20
	res = response.json()
21
	
22
	# 此处要根据具体的json结构进行解析
23
	jsonRes = json.loads(res['general_msg_list'])
24
	titleList = jsonpath.jsonpath(jsonRes, "$..title")
25
	urlList = jsonpath.jsonpath(jsonRes, "$..content_url")
26
27
	
28
	# 遍历 构造可存储字符串·
29
	for index in range(len(titleList)):
30
		title = titleList[index]
31
		url = urlList[index]
32
 
33
		scvStr = "%s,%s,\n" % (title, url)
34
		with open("info.csv", "a+", encoding="gbk", newline='') as f:
35
			f.write(scvStr)

代码会将历史消息标题和连接存储到 csv 文件中

参考博客 jQuery插件开发精品教程,让你的jQuery提升一个台阶

1
// 自调用匿名空间(避免代码命名冲突)
2
;(function(window, document, undefined) {
3
    //定义Beautifier的构造函数
4
    var Beautifier = function(ele, opt) {
5
        this.$element = ele,
6
        this.defaults = {
7
            'color': 'red',
8
            'fontSize': '12px',
9
            'textDecoration': 'none'
10
        },
11
        this.options = $.extend({}, this.defaults, opt)
12
    }
13
    //定义Beautifier的方法
14
    Beautifier.prototype = {
15
        beautify: function() {
16
            return this.$element.css({
17
                'color': this.options.color,
18
                'fontSize': this.options.fontSize,
19
                'textDecoration': this.options.textDecoration
20
            });
21
        }
22
    }
23
    //在插件中使用Beautifier对象
24
    window.myPlugin = function(options) {
25
        //创建Beautifier的实体
26
        var beautifier = new Beautifier(this, options);
27
        //调用其方法
28
        return beautifier.beautify();
29
    }
30
})(jQuery, window, document);

参考自 JS滚轮事件(mousewheel/DOMMouseScroll)了解
页面代码改造自 原生JS实现全屏滚动(无滚动条)

1
<!DOCTYPE html>
2
<html>
3
	<head>
4
		<meta charset="utf-8" />
5
		<title></title>
6
		<style type="text/css">
7
			* {
8
				margin: 0;
9
				padding: 0;
10
				font-size: 0;
11
			}
12
			.box {
13
				position: relative;
14
				width: 100%;
15
				height: 100vh;
16
				overflow: hidden;
17
			}
18
			ul {
19
				position: relative;
20
			}
21
			li {
22
				width: 100%;
23
				height: 100vh;
24
				font-size: 30px;
25
				position: relative;
26
				display: flex;
27
				justify-content: center;
28
				text-align: center;
29
			}
30
			
31
			.nav {
32
				position: fixed;
33
				right: 2%;
34
				top: 35%;
35
			}
36
			.nav li {
37
				width: 16px;
38
				height: 16px;
39
				border-radius: 50%;
40
				border: 1.5px solid #000;
41
				margin-bottom: 5px;
42
			}
43
		</style>
44
	</head>
45
	<body>
46
		<div class="box">
47
			<ul style="transition: 0.5s ease; top:0;">
48
				<li>一页面</li>
49
				<li>二页面</li>
50
				<li>三页面</li>
51
				<li>四页面</li>
52
			</ul>
53
		</div>
54
		<div class="nav">
55
			<ul>
56
				<li></li>
57
				<li></li>
58
				<li></li>
59
				<li></li>
60
			</ul>
61
		</div>
62
		<script type="text/javascript">
63
			/**
64
			 * 简易的事件添加方法封装
65
			 */
66
			var addEvent = (function(window, undefined) {
67
				var _eventCompat = function(event) {
68
					var type = event.type;
69
					if (type == 'DOMMouseScroll' || type == 'mousewheel') {
70
						event.delta = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
71
					}
72
					//alert(event.delta);
73
					if (event.srcElement && !event.target) {
74
						event.target = event.srcElement;
75
					}
76
					if (!event.preventDefault && event.returnValue !== undefined) {
77
						event.preventDefault = function() {
78
							event.returnValue = false;
79
						};
80
					}
81
					/*
82
					   ......其他一些兼容性处理 */
83
					return event;
84
				};
85
				if (window.addEventListener) {
86
					return function(el, type, fn, capture) {
87
						if (type === "mousewheel" && document.mozFullScreen !== undefined) {
88
							type = "DOMMouseScroll";
89
						}
90
						el.addEventListener(type, function(event) {
91
							fn.call(this, _eventCompat(event));
92
						}, capture || false);
93
					}
94
				} else if (window.attachEvent) {
95
					return function(el, type, fn, capture) {
96
						el.attachEvent("on" + type, function(event) {
97
							event = event || window.event;
98
							fn.call(el, _eventCompat(event));
99
						});
100
					}
101
				}
102
				return function() {};
103
			})(window);
104
105
			// 滚动锁
106
			var mousewheelLock = false;
107
			var pageIndex = 0,	// 页码
108
				pageNum = 4;	// 页数
109
			// 滚动动画时间	
110
			var scrollAnimateTime = 1200;
111
				
112
			var oUl = document.querySelector("ul");
113
			var navLis = document.querySelectorAll(".nav li");
114
			navLis[pageIndex].style.background = 'black';
115
			
116
			/** 添加鼠标滚轮事件 **/
117
			addEvent(oUl, 'mousewheel', function(event) {
118
				if (mousewheelLock) return ;
119
                mousewheelLock = true;
120
                // 向下滚动
121
                if (event.delta < 0) {
122
                    nextPage();
123
                } else {	// 向上滚动
124
                    prevPage();
125
                }
126
                setTimeout(function(){
127
                    mousewheelLock = false;
128
                }, scrollAnimateTime);
129
			});
130
			// 下一页
131
			function nextPage() {
132
				if (pageIndex < pageNum-1 ) {
133
					changePage(pageIndex++);
134
				}
135
			}
136
			// 上一页
137
			function prevPage() {
138
				if (pageIndex > 0 ) {
139
					changePage(pageIndex--);
140
				}
141
			}
142
            // 改变页面
143
			function changePage(selectedPageIndex) { 
144
				oUl.style.top = -selectedPageIndex * 100 + 'vh';
145
				for (var i=0; i<navLis.length; i++) {
146
					navLis[i].style.background = 'white';
147
				}
148
				navLis[pageIndex].style.background = 'black';
149
			}
150
151
		</script>
152
	</body>
153
</html>

1. clientWidth和clientHeigh 、 clientTop和clientLeft

  • clientWidth 表示可视区宽度,clientWidth = width + 左右padding

  • clientHeigh 表示可视区高度,clientHeigh = height + 上下padding

  • clientTop 表示实际宽度,clientTop = boder.top(上边框的宽度)

    4,clientLeft的实际宽度
    
        clientLeft = boder.left(左边框的宽度)

    二、offsetWidth和offsetHight 、 offsetTop和offsetLeft

    1,offsetWidth的实际宽度
    
       offsetWidth = width + 左右padding + 左右boder
    
    2,offsetHeith的实际高度
    
                 offsetHeith = height + 上下padding + 上下boder
    
    3,offsetTop实际宽度
    
                  offsetTop:当前元素 上边框 外边缘 到 最近的已定位父级(offsetParent) 上边框 内边缘的 距离。如果父
    
            级都没有定位,则分别是到body 顶部 和左边的距离
    
            4,offsetLeft实际宽度
    
         offsetLeft:当前元素 左边框 外边缘 到 最近的已定位父级(offsetParent) 左边框 内边缘的            距离。如果父级都没有定位,则分别是到body 顶部 和左边的距离

    三、scrollWidth和scrollHeight 、 scrollTop和scrollLeft

    1,scrollWidth实际宽度
    
           scrollWidth:获取指定标签内容层的真实宽度(可视区域宽度+被隐藏区域宽度)。
      2,scrollHeight的实际高度



scrollHeight:获取指定标签内容层的真实高度(可视区域高度+被隐藏区域高度)

3,scrollTop

       scrollTop :内容层顶部 到 可视区域顶部的距离。

 实例:var scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;

 持续获取高度的方式:

window.addEventListener(‘scroll’, ()=>{

var scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;

});

4,scrollLeft

scrollLeft:内容层左端 到 可视区域左端的距离.
1
2

当文本内容长度超过容器元素宽度时,要求文本自动滚动,如下图

文本过长自动滚动)百度

有以下两点限制:

  • 文本不能全部显示出来,不能超出容器宽度
  • 文本不能自动换行

原理:当子元素的实际内容长度 scrollWith 大于可视区宽度 clientWith 时,利用 position:relative 的属性来对子元素进行左右移动,使子元素的所有内容能够在父元素的可视区宽度内展示出来

就相当于我们透过一个小窗口看窗外经过的火车一样,小窗口是父元素,而经过的火车是子元素

element.clientwidth 表示元素的可视区宽度, element.scrollWidth 表示元素内容宽度

父元素要设置 overflow: hidden ,以便能够隐藏子元素超出内容。但是子元素不能够设置 overflow: hidden ,因为子元素设置了该属性,那么子元素的 clientWidth 就和 scrollWidth 相等了。
百度
下面是源码,复制即可运行。

1
<!doctype html>
2
<html lang="zh">
3
<head>
4
    <meta charset="UTF-8">
5
    <title>文本过长自动滚动</title>
6
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
7
    <style type="text/css">
8
        * {
9
            margin: 0;
10
            padding: 0;
11
        }
12
        #parentEle {
13
			margin: 0 auto;
14
			border: 1px solid blue;
15
			/* 固定宽度 */
16
            width: 100px;
17
			/* 超过宽度自动隐藏 */
18
            overflow: hidden;
19
        }
20
		#sonEle {
21
			/* 相对位置 */
22
			position: relative;
23
			/* 不能自动换行 */
24
			white-space: nowrap;
25
		}
26
    </style>
27
</head>
28
<body>
29
    <div id="parentEle">
30
        <div id="sonEle">
31
            春草带雨晚来急,野渡无人舟自横
32
        </div>
33
    </div>
34
    <script>
35
    	$(function(){
36
            var sonEle = document.querySelector("#sonEle");
37
			// 子元素实际内容长度超过可视区宽度
38
            if (sonEle.scrollWidth > sonEle.clientWidth) {
39
                var d = sonEle.scrollWidth - sonEle.clientWidth;
40
				$(sonEle).animate({left: -d +'px'}, 3000);
41
                $(sonEle).animate({left:'0px'}, 3000);
42
                setInterval(function(){
43
                    $(sonEle).animate({left: -d +'px'}, 3000);
44
                    $(sonEle).animate({left:'0px'}, 3000);
45
                }, 6000);
46
            }
47
        })
48
    </script>
49
</body>
50
</html>

解析Subject

了解 Subject

继承图

WebDelegatingSubject继承图

Subject

Subject方法属性

Subject内部类Builder的方法属性

DelegatingSubject

DelegatingSubject方法属性

WebDelegatingSubject

WebDelegatingSubject方法属性

分析 Subject

在登录流程中,我们经常用到的方法有 getPrincipal()getSession()login()logout

getPrincipal(): Object

该方法在 DelegatingSubject 实现

1
private Object getPrimaryPrincipal(PrincipalCollection principals) {
2
    if (!isEmpty(principals)) {
3
        return principals.getPrimaryPrincipal();
4
    }
5
    return null;
6
}
7
8
/**
9
 * @see Subject#getPrincipal()
10
 */
11
public Object getPrincipal() {
12
    return getPrimaryPrincipal(getPrincipals());
13
}
14
15
public PrincipalCollection getPrincipals() {
16
    List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack();
17
    return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0);
18
}
19
......
20
// line 169
21
private List<PrincipalCollection> getRunAsPrincipalsStack() {
22
    Session session = getSession(false);
23
    if (session != null) {
24
        return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
25
    }
26
    return null;
27
}

getSession() : Session

该方法在 DelegatingSubject 中实现的

1
// line 314
2
public Session getSession() {
3
    return getSession(true);
4
}
5
6
public Session getSession(boolean create) {
7
    if (log.isTraceEnabled()) {
8
        log.trace("attempting to get session; create = " + create +
9
                "; session is null = " + (this.session == null) +
10
                "; session has id = " + (this.session != null && session.getId() != null));
11
    }
12
13
    if (this.session == null && create) {
14
15
        //added in 1.2:
16
        if (!isSessionCreationEnabled()) {
17
            String msg = "Session creation has been disabled for the current subject.  This exception indicates " +
18
                    "that there is either a programming error (using a session when it should never be " +
19
                    "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
20
                    "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
21
                    "for more.";
22
            throw new DisabledSessionException(msg);
23
        }
24
25
        log.trace("Starting session for host {}", getHost());
26
        SessionContext sessionContext = createSessionContext();
27
        Session session = this.securityManager.start(sessionContext);
28
        this.session = decorate(session);
29
    }
30
    return this.session;
31
}
32
33
protected SessionContext createSessionContext() {
34
    SessionContext sessionContext = new DefaultSessionContext();
35
    if (StringUtils.hasText(host)) {
36
        sessionContext.setHost(host);
37
    }
38
    return sessionContext;
39
}

getSession() 等同于 getSession(true) ,如果 session 不存在,则创建一个

解析SubjectContext

了解SubjectContext

继承图

DefaultSubjectContext继承图

SubjectContext

SubjectContext属性方法

MapContext

MapContext属性方法

DefaultSubjectContext

DefaultSubjectContext属性方法

DefaultWebSubjectContext

DefaultWebSubjectContext属性方法

小结

MapContext 的Map对象 backingMap 专门用来保存各种类型的变量,看它的定义就可明白

1
private final Map<String, Object> backingMap;
2
3
public MapContext() {
4
    this.backingMap = new HashMap<String, Object>();
5
}

而 DefaultSubjectContext 和 DefaultWebSubjectContext 继承了这一点,并在此基础上添加了多个 setter 和 getter ,用来向map添加元素,以及从map中获取元素。

但是要注意名称如 resolveXxx 这样的方法。

理解 SubjectContext

setXxx 方法

1
public void setSecurityManager(SecurityManager securityManager) {
2
    nullSafePut(SECURITY_MANAGER, securityManager);
3
}

nullSafePut(String, Object) 方法继承自 MapContext

1
// line 73
2
/**
3
 * Places a value in this context map under the given key only if the given {@code value} argument is not null.
4
 *
5
 * @param key   the attribute key under which the non-null value will be stored
6
 * @param value the non-null value to store.  If {@code null}, this method does nothing and returns immediately.
7
 */
8
protected void nullSafePut(String key, Object value) {
9
    if (value != null) {
10
        put(key, value);
11
    }
12
}
13
......
14
// line 105
15
public Object put(String s, Object o) {
16
    return backingMap.put(s, o);
17
}

setXxx 方法就是将 Xxx 添加到 backingMap 中

getXxx 方法

1
public SecurityManager getSecurityManager() {
2
    return getTypedValue(SECURITY_MANAGER, SecurityManager.class);
3
}

getTypedValue 同样继承自 MapContext

1
// line 48
2
/**
3
 * Performs a {@link #get get} operation but additionally ensures that the value returned is of the specified
4
 * {@code type}.  If there is no value, {@code null} is returned.
5
 *
6
 * @param key  the attribute key to look up a value
7
 * @param type the expected type of the value
8
 * @param <E>  the expected type of the value
9
 * @return the typed value or {@code null} if the attribute does not exist.
10
 */
11
@SuppressWarnings({"unchecked"})
12
protected <E> E getTypedValue(String key, Class<E> type) {
13
    E found = null;
14
    Object o = backingMap.get(key);
15
    if (o != null) {
16
        if (!type.isAssignableFrom(o.getClass())) {
17
            String msg = "Invalid object found in SubjectContext Map under key [" + key + "].  Expected type " +
18
                    "was [" + type.getName() + "], but the object under that key is of type " +
19
                    "[" + o.getClass().getName() + "].";
20
            throw new IllegalArgumentException(msg);
21
        }
22
        found = (E) o;
23
    }
24
    return found;
25
}

getXxx 方法中调用 getTypedValue(String, Class) 。在该方法中,根据 key 从 backingMap 中获取值,然后判断该值是否是 Class 对象。

resolveXxx 方法

resolveSecurityManager()

1
// line 96
2
public SecurityManager resolveSecurityManager() {
3
    SecurityManager securityManager = getSecurityManager();
4
    if (securityManager == null) {
5
        if (log.isDebugEnabled()) {
6
            log.debug("No SecurityManager available in subject context map.  " +
7
                    "Falling back to SecurityUtils.getSecurityManager() lookup.");
8
        }
9
        try {
10
            securityManager = SecurityUtils.getSecurityManager();
11
        } catch (UnavailableSecurityManagerException e) {
12
            if (log.isDebugEnabled()) {
13
                log.debug("No SecurityManager available via SecurityUtils.  Heuristics exhausted.", e);
14
            }
15
        }
16
    }
17
    return securityManager;
18
}

先调用 getSecurityManager() 从 backingMap 中获取对象;

为空的话,则调用 SecurityUtils.getSecurityManager()

resolveSession()

1
public Session resolveSession() {
2
    Session session = getSession();
3
    if (session == null) {
4
        //try the Subject if it exists:
5
        Subject existingSubject = getSubject();
6
        if (existingSubject != null) {
7
            session = existingSubject.getSession(false);
8
        }
9
    }
10
    return session;
11
}
  • 先调用 getSession() 从 backingMap 中获取对象;
  • 为空的话,则去 subject 中获取

resolvePrincipals()

1
public PrincipalCollection resolvePrincipals() {
2
        PrincipalCollection principals = getPrincipals();
3
4
        if (isEmpty(principals)) {
5
            //check to see if they were just authenticated:
6
            AuthenticationInfo info = getAuthenticationInfo();
7
            if (info != null) {
8
                principals = info.getPrincipals();
9
            }
10
        }
11
12
        if (isEmpty(principals)) {
13
            Subject subject = getSubject();
14
            if (subject != null) {
15
                principals = subject.getPrincipals();
16
            }
17
        }
18
19
        if (isEmpty(principals)) {
20
            //try the session:
21
            Session session = resolveSession();
22
            if (session != null) {
23
                principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
24
            }
25
        }
26
27
        return principals;
28
    }
  • 先调用 getPrincipals() 从 backingMap 中获取对象;
  • 为空的话,则去AuthenticationInfo对象中获取;
  • 还为空的话,则去Subject对象中获取;
  • 还为空的话,则去Session对象中获取。

SecurityUtils.getSubject()

1
// line 38
2
/**
3
 * Returns the currently accessible {@code Subject} available to the calling code depending on
4
 * runtime environment.
5
 * <p/>
6
 * This method is provided as a way of obtaining a {@code Subject} without having to resort to
7
 * implementation-specific methods.  It also allows the Shiro team to change the underlying implementation of
8
 * this method in the future depending on requirements/updates without affecting your code that uses it.
9
 *
10
 * @return the currently accessible {@code Subject} accessible to the calling code.
11
 * @throws IllegalStateException if no {@link Subject Subject} instance or
12
 *                               {@link SecurityManager SecurityManager} instance is available with which to obtain
13
 *                               a {@code Subject}, which which is considered an invalid application configuration
14
 *                               - a Subject should <em>always</em> be available to the caller.
15
 */
16
public static Subject getSubject() {
17
    Subject subject = ThreadContext.getSubject();
18
    if (subject == null) {
19
        subject = (new Subject.Builder()).buildSubject();
20
        ThreadContext.bind(subject);
21
    }
22
    return subject;
23
}

ThreadContext.getSubject()

从线程局部变量 ThreadLocal<Map<String, Object>> resource 里,获取 key 为 ThreadContext.class.getName() + "_SUBJECT_KEY" 的 Subject 对象

1
/**
2
 * Convenience method that simplifies retrieval of a thread-bound Subject.  If there is no
3
 * Subject bound to the thread, this method returns <tt>null</tt>.  It is merely a convenient wrapper
4
 * for the following:
5
 * <p/>
6
 * <code>return (Subject)get( SUBJECT_KEY );</code>
7
 * <p/>
8
 * This method only returns the bound value if it exists - it does not remove it
9
 * from the thread.  To remove it, one must call {@link #unbindSubject() unbindSubject()} instead.
10
 *
11
 * @return the Subject object bound to the thread, or <tt>null</tt> if there isn't one bound.
12
 * @since 0.2
13
 */
14
public static Subject getSubject() {
15
	// public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
16
    return (Subject) get(SUBJECT_KEY);
17
}
18
19
20
 /**
21
 * Returns the object for the specified <code>key</code> that is bound to
22
 * the current thread.
23
 *
24
 * @param key the key that identifies the value to return
25
 * @return the object keyed by <code>key</code> or <code>null</code> if
26
 *         no value exists for the specified <code>key</code>
27
 */
28
public static Object get(Object key) {
29
    if (log.isTraceEnabled()) {
30
        String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
31
        log.trace(msg);
32
    }
33
34
    Object value = getValue(key);
35
    if ((value != null) && log.isTraceEnabled()) {
36
        String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
37
                key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
38
        log.trace(msg);
39
    }
40
    return value;
41
}
42
43
44
/**
45
 * Returns the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there
46
 * is no value for that {@code key}.
47
 *
48
 * @param key the map key to use to lookup the value
49
 * @return the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there
50
 *         is no value for that {@code key}.
51
 * @since 1.0
52
 */
53
private static Object getValue(Object key) {
54
	// private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
55
    Map<Object, Object> perThreadResources = resources.get();
56
    return perThreadResources != null ? perThreadResources.get(key) : null;
57
}

(new Subject.Builder()).buildSubject()

(new Subject.Builder())

这里暂时不去穷究 SecurityUtils.getSecurityManager()

1
/**
2
 * Constructs a new {@link Subject.Builder} instance, using the {@code SecurityManager} instance available
3
 * to the calling code as determined by a call to {@link org.apache.shiro.SecurityUtils#getSecurityManager()}
4
 * to build the {@code Subject} instance.
5
 */
6
public Builder() {
7
    this(SecurityUtils.getSecurityManager());
8
}
9
10
/**
11
 * Constructs a new {@link Subject.Builder} instance which will use the specified {@code SecurityManager} when
12
 * building the {@code Subject} instance.
13
 *
14
 * @param securityManager the {@code SecurityManager} to use when building the {@code Subject} instance.
15
 */
16
public Builder(SecurityManager securityManager) {
17
    if (securityManager == null) {
18
        throw new NullPointerException("SecurityManager method argument cannot be null.");
19
    }
20
    this.securityManager = securityManager;
21
    this.subjectContext = newSubjectContextInstance();
22
    if (this.subjectContext == null) {
23
        throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
24
                "cannot be null.");
25
    }
26
    this.subjectContext.setSecurityManager(securityManager);
27
}

(new Subject.Builder()).buildSubject()

1
/**
2
 * Creates and returns a new {@code Subject} instance reflecting the cumulative state acquired by the
3
 * other methods in this class.
4
 * <p/>
5
 * This {@code Builder} instance will still retain the underlying state after this method is called - it
6
 * will not clear it; repeated calls to this method will return multiple {@link Subject} instances, all
7
 * reflecting the exact same state.  If a new (different) {@code Subject} is to be constructed, a new
8
 * {@code Builder} instance must be created.
9
 * <p/>
10
 * <b>Note</b> that the returned {@code Subject} instance is <b>not</b> automatically bound to the application
11
 * (thread) for further use.  That is,
12
 * {@link org.apache.shiro.SecurityUtils SecurityUtils}.{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}
13
 * will not automatically return the same instance as what is returned by the builder.  It is up to the
14
 * framework developer to bind the returned {@code Subject} for continued use if desired.
15
 *
16
 * @return a new {@code Subject} instance reflecting the cumulative state acquired by the
17
 *         other methods in this class.
18
 */
19
public Subject buildSubject() {
20
    return this.securityManager.createSubject(this.subjectContext);
21
}
22
23
// 在构造函数中 `this.subjectContext = newSubjectContextInstance();`
24
/**
25
 * Creates a new {@code SubjectContext} instance to be used to populate with subject contextual data that
26
 * will then be sent to the {@code SecurityManager} to create a new {@code Subject} instance.
27
 *
28
 * @return a new {@code SubjectContext} instance
29
 */
30
protected SubjectContext newSubjectContextInstance() {
31
    return new DefaultSubjectContext();
32
}

ThreadContext.bind(subject);

把 subject 绑定到线程内部

1
/**
2
 * Convenience method that simplifies binding a Subject to the ThreadContext.
3
 * <p/>
4
 * <p>The method's existence is to help reduce casting in your own code and to simplify remembering of
5
 * ThreadContext key names.  The implementation is simple in that, if the Subject is not <tt>null</tt>,
6
 * it binds it to the thread, i.e.:
7
 * <p/>
8
 * <pre>
9
 * if (subject != null) {
10
 *     put( SUBJECT_KEY, subject );
11
 * }</pre>
12
 *
13
 * @param subject the Subject object to bind to the thread.  If the argument is null, nothing will be done.
14
 * @since 0.2
15
 */
16
public static void bind(Subject subject) {
17
    if (subject != null) {
18
        put(SUBJECT_KEY, subject);
19
    }
20
}

小结

SecurityUtils.getSubject()ThreadContext.getSubject() 来获取subject;如果为null,则调用 (new Subject.Builder()).buildSubject() 来创建subject,在该方法内部最终调用 SecurityManager.createSubject(subjectContext)

了解 Realm

继承图

UserRealm

Realm

1571724441972

CachingRealm

1571724474817

AuthenticatingRealm

AuthenticatingRealm

AuthorizingRealm

AuthorizingRealm

UserRealm

通常我们自定义的 realm——UserRealm

1571734551679

理解Realm

getAuthenticationInfo(AuthenticationToken): AuthenticationInfo

1. AuthenticationRealm | getAuthenticationInfo

1
// line 541
2
/**
3
 * This implementation functions as follows:
4
 * <ol>
5
 * <li>It attempts to acquire any cached {@link AuthenticationInfo} corresponding to the specified
6
 * {@link AuthenticationToken} argument.  If a cached value is found, it will be used for credentials matching,
7
 * alleviating the need to perform any lookups with a data source.</li>
8
 * <li>If there is no cached {@link AuthenticationInfo} found, delegate to the
9
 * {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)} method to perform the actual
10
 * lookup.  If authentication caching is enabled and possible, any returned info object will be
11
 * {@link #cacheAuthenticationInfoIfPossible(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) cached}
12
 * to be used in future authentication attempts.</li>
13
 * <li>If an AuthenticationInfo instance is not found in the cache or by lookup, {@code null} is returned to
14
 * indicate an account cannot be found.</li>
15
 * <li>If an AuthenticationInfo instance is found (either cached or via lookup), ensure the submitted
16
 * AuthenticationToken's credentials match the expected {@code AuthenticationInfo}'s credentials using the
17
 * {@link #getCredentialsMatcher() credentialsMatcher}.  This means that credentials are always verified
18
 * for an authentication attempt.</li>
19
 * </ol>
20
 *
21
 * @param token the submitted account principal and credentials.
22
 * @return the AuthenticationInfo corresponding to the given {@code token}, or {@code null} if no
23
 *         AuthenticationInfo could be found.
24
 * @throws AuthenticationException if authentication failed.
25
 */
26
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
27
28
    AuthenticationInfo info = getCachedAuthenticationInfo(token);
29
    if (info == null) {
30
        //otherwise not cached, perform the lookup:
31
        info = doGetAuthenticationInfo(token);
32
        log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
33
        if (token != null && info != null) {
34
            cacheAuthenticationInfoIfPossible(token, info);
35
        }
36
    } else {
37
        log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
38
    }
39
40
    if (info != null) {
41
        assertCredentialsMatch(token, info);
42
    } else {
43
        log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
44
    }
45
46
    return info;
47
}
48
......
49
// line 696
50
/**
51
 * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
52
 * authentication token.
53
 * <p/>
54
 * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
55
 * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
56
 * log-in logic in addition to just retrieving data - it is up to the Realm implementation.
57
 * <p/>
58
 * A {@code null} return value means that no account could be associated with the specified token.
59
 *
60
 * @param token the authentication token containing the user's principal and credentials.
61
 * @return an {@link AuthenticationInfo} object containing account data resulting from the
62
 *         authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
63
 * @throws AuthenticationException if there is an error acquiring data or performing
64
 *                                 realm-specific authentication logic for the specified <tt>token</tt>
65
 */
66
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

2. AuthenticationRealm | getCachedAuthenticationInfo(token)

1
// line 471
2
3
/**
4
 * Returns any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently
5
 * isn't any cached data.
6
 *
7
 * @param token the token submitted during the authentication attempt.
8
 * @return any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently
9
 *         isn't any cached data.
10
 * @since 1.2
11
 */
12
private AuthenticationInfo getCachedAuthenticationInfo(AuthenticationToken token) {
13
    AuthenticationInfo info = null;
14
15
    Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
16
    if (cache != null && token != null) {
17
        log.trace("Attempting to retrieve the AuthenticationInfo from cache.");
18
        Object key = getAuthenticationCacheKey(token);
19
        info = cache.get(key);
20
        if (info == null) {
21
            log.trace("No AuthorizationInfo found in cache for key [{}]", key);
22
        } else {
23
            log.trace("Found cached AuthorizationInfo for key [{}]", key);
24
        }
25
    }
26
27
    return info;
28
}
29
...... 
30
// line 419
31
/**
32
 * Returns any available {@link Cache} instance to use for authentication caching.  This functions as follows:
33
 * <ol>
34
 * <li>If an {@link #setAuthenticationCache(org.apache.shiro.cache.Cache) authenticationCache} has been explicitly
35
 * configured (it is not null), it is returned.</li>
36
 * <li>If there is no {@link #getAuthenticationCache() authenticationCache} configured:
37
 * <ol>
38
 * <li>If authentication caching is {@link #isAuthenticationCachingEnabled() enabled}, any available
39
 * {@link #getCacheManager() cacheManager} will be consulted to obtain an available authentication cache.
40
 * </li>
41
 * <li>If authentication caching is disabled, this implementation does nothing.</li>
42
 * </ol>
43
 * </li>
44
 * </ol>
45
 *
46
 * @return any available {@link Cache} instance to use for authentication caching.
47
 */
48
private Cache<Object, AuthenticationInfo> getAvailableAuthenticationCache() {
49
    Cache<Object, AuthenticationInfo> cache = getAuthenticationCache();
50
    boolean authcCachingEnabled = isAuthenticationCachingEnabled();
51
    if (cache == null && authcCachingEnabled) {
52
        cache = getAuthenticationCacheLazy();
53
    }
54
    return cache;
55
}
56
57
/**
58
 * Checks to see if the authenticationCache class attribute is null, and if so, attempts to acquire one from
59
 * any configured {@link #getCacheManager() cacheManager}.  If one is acquired, it is set as the class attribute.
60
 * The class attribute is then returned.
61
 *
62
 * @return an available cache instance to be used for authentication caching or {@code null} if one is not available.
63
 * @since 1.2
64
 */
65
private Cache<Object, AuthenticationInfo> getAuthenticationCacheLazy() {
66
67
    if (this.authenticationCache == null) {
68
69
        log.trace("No authenticationCache instance set.  Checking for a cacheManager...");
70
71
        CacheManager cacheManager = getCacheManager();
72
73
        if (cacheManager != null) {
74
            String cacheName = getAuthenticationCacheName();
75
            log.debug("CacheManager [{}] configured.  Building authentication cache '{}'", cacheManager, cacheName);
76
            this.authenticationCache = cacheManager.getCache(cacheName);
77
        }
78
    }
79
80
    return this.authenticationCache;
81
}
82
......
83
// line 611
84
/**
85
 * Returns the key under which {@link AuthenticationInfo} instances are cached if authentication caching is enabled.
86
 * This implementation defaults to returning the token's
87
 * {@link org.apache.shiro.authc.AuthenticationToken#getPrincipal() principal}, which is usually a username in
88
 * most applications.
89
 * <h3>Cache Invalidation on Logout</h3>
90
 * <b>NOTE:</b> If you want to be able to invalidate an account's cached {@code AuthenticationInfo} on logout, you
91
 * must ensure the {@link #getAuthenticationCacheKey(org.apache.shiro.subject.PrincipalCollection)} method returns
92
 * the same value as this method.
93
 *
94
 * @param token the authentication token for which any successful authentication will be cached.
95
 * @return the cache key to use to cache the associated {@link AuthenticationInfo} after a successful authentication.
96
 * @since 1.2
97
 */
98
protected Object getAuthenticationCacheKey(AuthenticationToken token) {
99
    return token != null ? token.getPrincipal() : null;
100
}

从缓存 authenticationCache 中获取 authenticationInfo,从 cacheManager 中获取缓存。

问题:

  1. 是什么时候将 authenticationInfo 存入缓存的?

3. UserRealm | doGetAuthenticationInfo(token)

自定义Realm实现 doGetAuthenticationInfo(token) 方法

1
/**
2
 * 登录认证
3
 */
4
@Override
5
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
6
{
7
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
8
    String username = upToken.getUsername();
9
    String password = "";
10
    if (upToken.getPassword() != null)
11
    {
12
        password = new String(upToken.getPassword());
13
    }
14
15
    SysUser user = null;
16
    try
17
    {
18
        user = loginService.login(username, password);
19
    }
20
    catch (CaptchaException e)
21
    {
22
        throw new AuthenticationException(e.getMessage(), e);
23
    }
24
    catch (UserNotExistsException e)
25
    {
26
        throw new UnknownAccountException(e.getMessage(), e);
27
    }
28
    catch (UserPasswordNotMatchException e)
29
    {
30
        throw new IncorrectCredentialsException(e.getMessage(), e);
31
    }
32
    catch (UserPasswordRetryLimitExceedException e)
33
    {
34
        throw new ExcessiveAttemptsException(e.getMessage(), e);
35
    }
36
    catch (UserBlockedException e)
37
    {
38
        throw new LockedAccountException(e.getMessage(), e);
39
    }
40
    catch (RoleBlockedException e)
41
    {
42
        throw new LockedAccountException(e.getMessage(), e);
43
    }
44
    catch (Exception e)
45
    {
46
        log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
47
        throw new AuthenticationException(e.getMessage(), e);
48
    }
49
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
50
    return info;
51
}

SimpleAuthenticationInfo 是一个比较有意思的类,在下面会有具体的分析

4. AuthenticationRealm | cacheAuthenticationInfoIfPossible(token, info)

1
// line 498
2
/**
3
 * Caches the specified info if authentication caching
4
 * {@link #isAuthenticationCachingEnabled(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) isEnabled}
5
 * for the specific token/info pair and a cache instance is available to be used.
6
 *
7
 * @param token the authentication token submitted which resulted in a successful authentication attempt.
8
 * @param info  the AuthenticationInfo to cache as a result of the successful authentication attempt.
9
 * @since 1.2
10
 */
11
private void cacheAuthenticationInfoIfPossible(AuthenticationToken token, AuthenticationInfo info) {
12
    if (!isAuthenticationCachingEnabled(token, info)) {
13
        log.debug("AuthenticationInfo caching is disabled for info [{}].  Submitted token: [{}].", info, token);
14
        //return quietly, caching is disabled for this token/info pair:
15
        return;
16
    }
17
18
    Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
19
    if (cache != null) {
20
        Object key = getAuthenticationCacheKey(token);
21
        cache.put(key, info);
22
        log.trace("Cached AuthenticationInfo for continued authentication.  key=[{}], value=[{}].", key, info);
23
    }
24
}

将 authenticationInfo 存入缓存中,不过 isAuthenticationCachingEnabled(token, info) 默认是 false。

5. AuthenticationRealm | assertCredentialsMatch

1
/**
2
 * Asserts that the submitted {@code AuthenticationToken}'s credentials match the stored account
3
 * {@code AuthenticationInfo}'s credentials, and if not, throws an {@link AuthenticationException}.
4
 *
5
 * @param token the submitted authentication token
6
 * @param info  the AuthenticationInfo corresponding to the given {@code token}
7
 * @throws AuthenticationException if the token's credentials do not match the stored account credentials.
8
 */
9
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
10
    CredentialsMatcher cm = getCredentialsMatcher();
11
    if (cm != null) {
12
        if (!cm.doCredentialsMatch(token, info)) {
13
            //not successful - throw an exception to indicate this:
14
            String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
15
            throw new IncorrectCredentialsException(msg);
16
        }
17
    } else {
18
        throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
19
                "credentials during authentication.  If you do not wish for credentials to be examined, you " +
20
                "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
21
    }
22
}

小结

  1. 从缓存中获取 AuthenticationInfo,如果开启了 AuthenticationInfo 缓存的话(AuthenticationRealm)
  2. 调用 doGetAuthenticationInfo(token) 获取 AuthentionInfo (AuthenticationRealm)
  3. 如果开启了 AuthenticationInfo 缓存的话,那么缓存它
  4. 调用 assertCredentialsMatch 判断 token 和 info 是否匹配
  5. 返回 AuthenticationInfo

SimpleAuthenticationInfo

1.

了解 Realm

继承图

UserRealm

Realm

1571724441972

CachingRealm

1571724474817

AuthenticatingRealm

AuthenticatingRealm

AuthorizingRealm

AuthorizingRealm

UserRealm

通常我们自定义的 realm——UserRealm

1571734551679

理解Realm

getAuthenticationInfo(AuthenticationToken): AuthenticationInfo

1. AuthenticationRealm | getAuthenticationInfo

1
// line 541
2
/**
3
 * This implementation functions as follows:
4
 * <ol>
5
 * <li>It attempts to acquire any cached {@link AuthenticationInfo} corresponding to the specified
6
 * {@link AuthenticationToken} argument.  If a cached value is found, it will be used for credentials matching,
7
 * alleviating the need to perform any lookups with a data source.</li>
8
 * <li>If there is no cached {@link AuthenticationInfo} found, delegate to the
9
 * {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)} method to perform the actual
10
 * lookup.  If authentication caching is enabled and possible, any returned info object will be
11
 * {@link #cacheAuthenticationInfoIfPossible(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) cached}
12
 * to be used in future authentication attempts.</li>
13
 * <li>If an AuthenticationInfo instance is not found in the cache or by lookup, {@code null} is returned to
14
 * indicate an account cannot be found.</li>
15
 * <li>If an AuthenticationInfo instance is found (either cached or via lookup), ensure the submitted
16
 * AuthenticationToken's credentials match the expected {@code AuthenticationInfo}'s credentials using the
17
 * {@link #getCredentialsMatcher() credentialsMatcher}.  This means that credentials are always verified
18
 * for an authentication attempt.</li>
19
 * </ol>
20
 *
21
 * @param token the submitted account principal and credentials.
22
 * @return the AuthenticationInfo corresponding to the given {@code token}, or {@code null} if no
23
 *         AuthenticationInfo could be found.
24
 * @throws AuthenticationException if authentication failed.
25
 */
26
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
27
28
    AuthenticationInfo info = getCachedAuthenticationInfo(token);
29
    if (info == null) {
30
        //otherwise not cached, perform the lookup:
31
        info = doGetAuthenticationInfo(token);
32
        log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
33
        if (token != null && info != null) {
34
            cacheAuthenticationInfoIfPossible(token, info);
35
        }
36
    } else {
37
        log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
38
    }
39
40
    if (info != null) {
41
        assertCredentialsMatch(token, info);
42
    } else {
43
        log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
44
    }
45
46
    return info;
47
}
48
......
49
// line 696
50
/**
51
 * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
52
 * authentication token.
53
 * <p/>
54
 * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
55
 * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
56
 * log-in logic in addition to just retrieving data - it is up to the Realm implementation.
57
 * <p/>
58
 * A {@code null} return value means that no account could be associated with the specified token.
59
 *
60
 * @param token the authentication token containing the user's principal and credentials.
61
 * @return an {@link AuthenticationInfo} object containing account data resulting from the
62
 *         authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
63
 * @throws AuthenticationException if there is an error acquiring data or performing
64
 *                                 realm-specific authentication logic for the specified <tt>token</tt>
65
 */
66
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

2. AuthenticationRealm | getCachedAuthenticationInfo(token)

1
// line 471
2
3
/**
4
 * Returns any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently
5
 * isn't any cached data.
6
 *
7
 * @param token the token submitted during the authentication attempt.
8
 * @return any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently
9
 *         isn't any cached data.
10
 * @since 1.2
11
 */
12
private AuthenticationInfo getCachedAuthenticationInfo(AuthenticationToken token) {
13
    AuthenticationInfo info = null;
14
15
    Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
16
    if (cache != null && token != null) {
17
        log.trace("Attempting to retrieve the AuthenticationInfo from cache.");
18
        Object key = getAuthenticationCacheKey(token);
19
        info = cache.get(key);
20
        if (info == null) {
21
            log.trace("No AuthorizationInfo found in cache for key [{}]", key);
22
        } else {
23
            log.trace("Found cached AuthorizationInfo for key [{}]", key);
24
        }
25
    }
26
27
    return info;
28
}
29
...... 
30
// line 419
31
/**
32
 * Returns any available {@link Cache} instance to use for authentication caching.  This functions as follows:
33
 * <ol>
34
 * <li>If an {@link #setAuthenticationCache(org.apache.shiro.cache.Cache) authenticationCache} has been explicitly
35
 * configured (it is not null), it is returned.</li>
36
 * <li>If there is no {@link #getAuthenticationCache() authenticationCache} configured:
37
 * <ol>
38
 * <li>If authentication caching is {@link #isAuthenticationCachingEnabled() enabled}, any available
39
 * {@link #getCacheManager() cacheManager} will be consulted to obtain an available authentication cache.
40
 * </li>
41
 * <li>If authentication caching is disabled, this implementation does nothing.</li>
42
 * </ol>
43
 * </li>
44
 * </ol>
45
 *
46
 * @return any available {@link Cache} instance to use for authentication caching.
47
 */
48
private Cache<Object, AuthenticationInfo> getAvailableAuthenticationCache() {
49
    Cache<Object, AuthenticationInfo> cache = getAuthenticationCache();
50
    boolean authcCachingEnabled = isAuthenticationCachingEnabled();
51
    if (cache == null && authcCachingEnabled) {
52
        cache = getAuthenticationCacheLazy();
53
    }
54
    return cache;
55
}
56
57
/**
58
 * Checks to see if the authenticationCache class attribute is null, and if so, attempts to acquire one from
59
 * any configured {@link #getCacheManager() cacheManager}.  If one is acquired, it is set as the class attribute.
60
 * The class attribute is then returned.
61
 *
62
 * @return an available cache instance to be used for authentication caching or {@code null} if one is not available.
63
 * @since 1.2
64
 */
65
private Cache<Object, AuthenticationInfo> getAuthenticationCacheLazy() {
66
67
    if (this.authenticationCache == null) {
68
69
        log.trace("No authenticationCache instance set.  Checking for a cacheManager...");
70
71
        CacheManager cacheManager = getCacheManager();
72
73
        if (cacheManager != null) {
74
            String cacheName = getAuthenticationCacheName();
75
            log.debug("CacheManager [{}] configured.  Building authentication cache '{}'", cacheManager, cacheName);
76
            this.authenticationCache = cacheManager.getCache(cacheName);
77
        }
78
    }
79
80
    return this.authenticationCache;
81
}
82
......
83
// line 611
84
/**
85
 * Returns the key under which {@link AuthenticationInfo} instances are cached if authentication caching is enabled.
86
 * This implementation defaults to returning the token's
87
 * {@link org.apache.shiro.authc.AuthenticationToken#getPrincipal() principal}, which is usually a username in
88
 * most applications.
89
 * <h3>Cache Invalidation on Logout</h3>
90
 * <b>NOTE:</b> If you want to be able to invalidate an account's cached {@code AuthenticationInfo} on logout, you
91
 * must ensure the {@link #getAuthenticationCacheKey(org.apache.shiro.subject.PrincipalCollection)} method returns
92
 * the same value as this method.
93
 *
94
 * @param token the authentication token for which any successful authentication will be cached.
95
 * @return the cache key to use to cache the associated {@link AuthenticationInfo} after a successful authentication.
96
 * @since 1.2
97
 */
98
protected Object getAuthenticationCacheKey(AuthenticationToken token) {
99
    return token != null ? token.getPrincipal() : null;
100
}

从缓存 authenticationCache 中获取 authenticationInfo,从 cacheManager 中获取缓存。

问题:

  1. 是什么时候将 authenticationInfo 存入缓存的?

3. UserRealm | doGetAuthenticationInfo(token)

自定义Realm实现 doGetAuthenticationInfo(token) 方法

1
/**
2
 * 登录认证
3
 */
4
@Override
5
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
6
{
7
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
8
    String username = upToken.getUsername();
9
    String password = "";
10
    if (upToken.getPassword() != null)
11
    {
12
        password = new String(upToken.getPassword());
13
    }
14
15
    SysUser user = null;
16
    try
17
    {
18
        user = loginService.login(username, password);
19
    }
20
    catch (CaptchaException e)
21
    {
22
        throw new AuthenticationException(e.getMessage(), e);
23
    }
24
    catch (UserNotExistsException e)
25
    {
26
        throw new UnknownAccountException(e.getMessage(), e);
27
    }
28
    catch (UserPasswordNotMatchException e)
29
    {
30
        throw new IncorrectCredentialsException(e.getMessage(), e);
31
    }
32
    catch (UserPasswordRetryLimitExceedException e)
33
    {
34
        throw new ExcessiveAttemptsException(e.getMessage(), e);
35
    }
36
    catch (UserBlockedException e)
37
    {
38
        throw new LockedAccountException(e.getMessage(), e);
39
    }
40
    catch (RoleBlockedException e)
41
    {
42
        throw new LockedAccountException(e.getMessage(), e);
43
    }
44
    catch (Exception e)
45
    {
46
        log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
47
        throw new AuthenticationException(e.getMessage(), e);
48
    }
49
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
50
    return info;
51
}

SimpleAuthenticationInfo 是一个比较有意思的类,在下面会有具体的分析

4. AuthenticationRealm | cacheAuthenticationInfoIfPossible(token, info)

1
// line 498
2
/**
3
 * Caches the specified info if authentication caching
4
 * {@link #isAuthenticationCachingEnabled(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) isEnabled}
5
 * for the specific token/info pair and a cache instance is available to be used.
6
 *
7
 * @param token the authentication token submitted which resulted in a successful authentication attempt.
8
 * @param info  the AuthenticationInfo to cache as a result of the successful authentication attempt.
9
 * @since 1.2
10
 */
11
private void cacheAuthenticationInfoIfPossible(AuthenticationToken token, AuthenticationInfo info) {
12
    if (!isAuthenticationCachingEnabled(token, info)) {
13
        log.debug("AuthenticationInfo caching is disabled for info [{}].  Submitted token: [{}].", info, token);
14
        //return quietly, caching is disabled for this token/info pair:
15
        return;
16
    }
17
18
    Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
19
    if (cache != null) {
20
        Object key = getAuthenticationCacheKey(token);
21
        cache.put(key, info);
22
        log.trace("Cached AuthenticationInfo for continued authentication.  key=[{}], value=[{}].", key, info);
23
    }
24
}

将 authenticationInfo 存入缓存中,不过 isAuthenticationCachingEnabled(token, info) 默认是 false。

5. AuthenticationRealm | assertCredentialsMatch

1
/**
2
 * Asserts that the submitted {@code AuthenticationToken}'s credentials match the stored account
3
 * {@code AuthenticationInfo}'s credentials, and if not, throws an {@link AuthenticationException}.
4
 *
5
 * @param token the submitted authentication token
6
 * @param info  the AuthenticationInfo corresponding to the given {@code token}
7
 * @throws AuthenticationException if the token's credentials do not match the stored account credentials.
8
 */
9
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
10
    CredentialsMatcher cm = getCredentialsMatcher();
11
    if (cm != null) {
12
        if (!cm.doCredentialsMatch(token, info)) {
13
            //not successful - throw an exception to indicate this:
14
            String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
15
            throw new IncorrectCredentialsException(msg);
16
        }
17
    } else {
18
        throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
19
                "credentials during authentication.  If you do not wish for credentials to be examined, you " +
20
                "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
21
    }
22
}

小结

  1. 从缓存中获取 AuthenticationInfo,如果开启了 AuthenticationInfo 缓存的话(AuthenticationRealm)
  2. 调用 doGetAuthenticationInfo(token) 获取 AuthentionInfo (AuthenticationRealm)
  3. 如果开启了 AuthenticationInfo 缓存的话,那么缓存它
  4. 调用 assertCredentialsMatch 判断 token 和 info 是否匹配
  5. 返回 AuthenticationInfo