登录过程源码分析
具体配置不在这里重复了,直接粘主要的ShiroFilterFactoryBean:
- @Bean(name="shiroFilter")
- public ShiroFilterFactoryBean shiroFilterFactoryBean()
- {
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
- shiroFilterFactoryBean.setSecurityManager(securityManager());
- shiroFilterFactoryBean.setLoginUrl("/login");
- shiroFilterFactoryBean.setUnauthorizedUrl("/404");
- Map filters = shiroFilterFactoryBean.getFilters();
- filters.put("authc", formAuthenticationFilter());
- Map definitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
- definitionMap.put("/test", "anon");
- definitionMap.put("/404", "anon");
- definitionMap.put("/order/**", "authc");
- definitionMap.put("/login", "authc");
- return shiroFilterFactoryBean;
- }
1-1
关于definitionMap中的value,应该经常见到authc,shiro内部中的key=authc对应的Filter就是FormAuthenticationFilter,这里只是为了讲述,因而重复定义:
当我们第一次访问受限资源的时候:shiro会调用onAccessDenied方法:
- protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
-
- if (isLoginRequest(request, response)) {
- if (isLoginSubmission(request, response)) {
- if (log.isTraceEnabled()) {
- log.trace("Login submission detected. Attempting to execute login.");
- }
- return executeLogin(request, response);
- } else {
- if (log.isTraceEnabled()) {
- log.trace("Login page view.");
- }
-
- return true;
- }
- } else {
-
- if (log.isTraceEnabled()) {
- log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
- "Authentication url [" + getLoginUrl() + "]");
- }
- saveRequestAndRedirectToLogin(request, response);
- return false;
- }
- }
1-2
这个结果会一层一层往上传,shiro有一堆filter链
到了登录页面之后,注意这里的action要为""哦,当然设置其他的也行,不过就需要在shiroFilterFactoryBean中多添加一个definitionMap了,
理由如下:
当我们输入好了需要提交的username和password之后,点击登录之后,因为action是""所以这个表单会提交到之前请求的url上,既(/login),因为我们已经在上述配置中配置了这个url请求拦截的filter,所以又会如1-2图所示,进入filter,然后会进行是否是Login请求判断:
- protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
- return pathsMatch(getLoginUrl(), request);
- }
- protected boolean pathsMatch(String path, ServletRequest request) {
- String requestURI = getPathWithinApplication(request);
- log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, requestURI);
- return pathsMatch(path, requestURI);
- }
pathMatch是PatternMatcher的实现类,他有两个实现类:通配符matcher:AntPathMatcher以及正则实现类:RegExPatternMatcher 具体实现不在这里论述
是登录请求之后,call executeLogin :
- protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
- AuthenticationToken token = createToken(request, response);
- if (token == null) {
- String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
- "must be created in order to execute a login attempt.";
- throw new IllegalStateException(msg);
- }
- try {
- Subject subject = getSubject(request, response);
- subject.login(token);
- return onLoginSuccess(token, subject, request, response);
- } catch (AuthenticationException e) {
- return onLoginFailure(token, e, request, response);
- }
- }
首先先生成token:FormAuthenticationFilter 的createToken的最终方法是返回UsernamePasswordToken
- protected AuthenticationToken createToken(String username, String password,
- boolean rememberMe, String host) {
- return new UsernamePasswordToken(username, password, rememberMe, host);
- }
然后获取subject(用户,或者第三方),call login
subject会将任务委托给SecurityManager:
- public void login(AuthenticationToken token) throws AuthenticationException {
- clearRunAsIdentitiesInternal();
- Subject subject = securityManager.login(this, token);
-
- PrincipalCollection principals;
-
- String host = null;
-
- if (subject instanceof DelegatingSubject) {
- DelegatingSubject delegating = (DelegatingSubject) subject;
-
- principals = delegating.principals;
- host = delegating.host;
- } else {
- principals = subject.getPrincipals();
- }
-
- if (principals == null || principals.isEmpty()) {
- String msg = "Principals returned from securityManager.login( token ) returned a null or " +
- "empty value. This value must be non null and populated with one or more elements.";
- throw new IllegalStateException(msg);
- }
- this.principals = principals;
- this.authenticated = true;
- if (token instanceof HostAuthenticationToken) {
- host = ((HostAuthenticationToken) token).getHost();
- }
- if (host != null) {
- this.host = host;
- }
- Session session = subject.getSession(false);
- if (session != null) {
- this.session = decorate(session);
- } else {
- this.session = null;
- }
- }
SecurityManager又会将任务委托给旗下的Realms:
- public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
- AuthenticationInfo info;
- try {
- info = authenticate(token);
- } catch (AuthenticationException ae) {
- try {
- onFailedLogin(token, ae, subject);
- } catch (Exception e) {
- if (log.isInfoEnabled()) {
- log.info("onFailedLogin method threw an " +
- "exception. Logging and propagating original AuthenticationException.", e);
- }
- }
- throw ae;
- }
-
- Subject loggedIn = createSubject(token, info, subject);
-
- onSuccessfulLogin(token, info, loggedIn);
-
- return loggedIn;
- }
Realm又是如何处理的呢:
在我的配置中SecurityManager只配置了一个Realm,have a look:
- public class MyLoginRealm extends AuthorizingRealm
- {
-
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
- {
- System.out.println("调用了MyLoginRealm的授权方法");
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- authorizationInfo.addRole("admin");
- authorizationInfo.addStringPermission("user:update");
- return authorizationInfo;
- }
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
- {
- System.out.println("调用了realm的登录验证的方法");
- return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), this.getName());
- }
- }
doGetAuthenticationinfo顾名思义,身份验证,返回Autheneticationinfo的子类,这个子类会被提交到上一层AuthenticatingRealm
- public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
-
- AuthenticationInfo info = getCachedAuthenticationInfo(token);
- if (info == null) {
-
- info = doGetAuthenticationInfo(token);
- log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
- if (token != null && info != null) {
- cacheAuthenticationInfoIfPossible(token, info);
- }
- } else {
- log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
- }
-
- if (info != null) {
- assertCredentialsMatch(token, info);
- } else {
- log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
- }
-
- return info;
- }
将结果返回给上层:ModularRealmAuthenticator,,这个Autenticator会通过Realm的个数选择不同的执行方式
- protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
- if (!realm.supports(token)) {
- String msg = "Realm [" + realm + "] does not support authentication token [" +
- token + "]. Please ensure that the appropriate Realm implementation is " +
- "configured correctly or that the realm accepts AuthenticationTokens of this type.";
- throw new UnsupportedTokenException(msg);
- }
- AuthenticationInfo info = realm.getAuthenticationInfo(token);
- if (info == null) {
- String msg = "Realm [" + realm + "] was unable to find account data for the " +
- "submitted AuthenticationToken [" + token + "].";
- throw new UnknownAccountException(msg);
- }
- return info;
- }
- protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
- assertRealmsConfigured();
- Collection realms = getRealms();
- if (realms.size() == 1) {
- return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
- } else {
- return doMultiRealmAuthentication(realms, authenticationToken);
- }
- }
之后又将结果传给上层:AbstractAuthenticator
- public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
-
- if (token == null) {
- throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
- }
-
- log.trace("Authentication attempt received for token [{}]", token);
-
- AuthenticationInfo info;
- try {
- info = doAuthenticate(token);
- if (info == null) {
- String msg = "No account information found for authentication token [" + token + "] by this " +
- "Authenticator instance. Please check that it is configured correctly.";
- throw new AuthenticationException(msg);
- }
- } catch (Throwable t) {
- AuthenticationException ae = null;
- if (t instanceof AuthenticationException) {
- ae = (AuthenticationException) t;
- }
- if (ae == null) {
-
-
- String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected " +
- "error? (Typical or expected login exceptions should extend from AuthenticationException).";
- ae = new AuthenticationException(msg, t);
- if (log.isWarnEnabled())
- log.warn(msg, t);
- }
- try {
- notifyFailure(token, ae);
- } catch (Throwable t2) {
- if (log.isWarnEnabled()) {
- String msg = "Unable to send notification for failed authentication attempt - listener error?. " +
- "Please check your AuthenticationListener implementation(s). Logging sending exception " +
- "and propagating original AuthenticationException instead...";
- log.warn(msg, t2);
- }
- }
-
-
- throw ae;
- }
-
- log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
-
- notifySuccess(token, info);
-
- return info;
- }
传传传
至于如何自定义呢:记住Shiro的核心是SecurityManager,因而要自定义基本离不开他
这里引用下开涛哥的讲解:Authorizer ,自定义的时候参考ModularRealmAuthorizer
PS:在这里延伸一点:我们可以自行配置Shiro从哪个表单属性中作为username或者password:如下所示,这样我们的表单页面中用户的账户对应的name属性更改为test_username 密码则改为test_password
- @Bean
- public FormAuthenticationFilter formAuthenticationFilter()
- {
- FormAuthenticationFilter filter = new FormAuthenticationFilter();
- filter.setLoginUrl("/login");
- filter.setUsernameParam("test_username");
- filter.setPasswordParam("test_password");
- return filter;
- }