1.shiro概述:
shiro是一個功能強大且易于使用的Java安全框架,它的認證,授權,加密和會話管理可以用于保護任何應用程序——來自從命令行應用程序、移動應用程序到最大的web和企業(yè)應用程序。
shiro為以下幾個方面提供應用程序的安全API(應用程序安全的4大基石):
Authentication - 提供用戶身份認證,俗稱登錄
Authorization - 訪問權限控制
Cryptography - 使用加密算法保護或者隱藏數(shù)據(jù)
Session Management - 用戶的會話管理
login方法調用:
/**
* 登錄
*/
@ApiOperation("用戶登錄")
@ResponseBody
@PostMapping(value = "/sys/login")
public R login(String username, String password, String captcha) {
String kaptcha = ShiroUtils.getKaptcha ( Constants.KAPTCHA_SESSION_KEY );
if (!captcha.equalsIgnoreCase ( kaptcha )) {
return R.error ( "驗證碼不正確" );
}
try {
/* ShiroUtils其實就是自定義對SecurityUtils做進一步優(yōu)化 */
/* 獲取項目管理信息 */
Subject subject = ShiroUtils.getSubject ();
UsernamePasswordToken token = new UsernamePasswordToken ( username, password );
subject.login ( token );
} catch (UnknownAccountException e) {
return R.error ( e.getMessage () );
} catch (IncorrectCredentialsException e) {
return R.error ( "賬號或密碼不正確" );
} catch (LockedAccountException e) {
return R.error ( "賬號已被鎖定,請聯(lián)系管理員" );
} catch (AuthenticationException e) {
return R.error ( "賬戶驗證失敗" );
}
return R.ok ();
}
2.shiro 認證機制
這個過程主要分為三個部分:
1:獲取客戶端輸入放入用戶名,密碼。
2:獲取數(shù)據(jù)源中存放的數(shù)據(jù)即相應的用戶名,密碼。
3:進行兩者的比對,判斷是否登錄操作成功。
先看一下shiro中是如何實現(xiàn)這個認證的過程的吧:

通過當前的用戶對象Subject執(zhí)行l(wèi)ogin()方法將用戶信息傳給Shiro的SecurityManager,而這個SecurityManager會將用戶信息委托給內部登錄模塊,由內部登錄模塊來調用Realm中的方法來進行數(shù)據(jù)比對進而判斷是否登錄成功
3. Subject,SecurityManager,Realm
3.1 Subject
概述:官方文檔
Subject
When you’re securing your application, probably the most relevant questions to ask yourself are, “Who is the current user?” or “Is the current user allowed to do X”? It is common for us to ask ourselves these questions as we're writing code or designing user interfaces: applications are usually built based on user stories, and you want functionality represented (and secured) based on a per-user basis. So, the most natural way for us to think about security in our application is based on the current user. Shiro’s API fundamentally represents this way of thinking in its Subject concept.
The word Subject is a security term that basically means "the currently executing user". It's just not called a 'User' because the word 'User' is usually associated with a human being. In the security world, the term 'Subject' can mean a human being, but also a 3rd party process, daemon account, or anything similar. It simply means 'the thing that is currently interacting with the software'. For most intents and purposes though, you can think of this as Shiro’s ‘User’ concept. You can easily acquire the Shiro Subject anywhere in your code as shown in Listing 1 below.
大意就是:subject指的是當前用戶,因為我們人的思維更傾向于某個用戶有某個角色,因此可以理解為基于當前用戶。(不過在安全領域,術語“Subject”可以指一個人,也可以指第三方進程、守護進程帳戶或任何類似的東西。)
它的獲取方法在官方文檔中定義為:相信在我上面的部分項目代碼中大家也已經看到了,這塊用法是固定的:
通過SecurityUtils工具類直接調用:
Subject currentUser = SecurityUtils.getSubject();
一旦您獲得了subject,就可以立即訪問當前用戶使用Shiro想要做的90%的事情,比如登錄、注銷、訪問他們的會話、執(zhí)行授權檢查,等等.
3.2 SecurityManager
SecurityManager是shiro架構核心,協(xié)調內部安全組件(如登錄,授權,數(shù)據(jù)源等),用來管理所有的subject。 它負責安全認證與授權。Shiro本身已經實現(xiàn)了所有的細節(jié),用戶可以完全把它當做一個黑盒來使用。SecurityUtils對象,本質上就是一個工廠類似Spring中的ApplicationContext。Subject是初學者比較難于理解的對象,很多人以為它可以等同于User,其實不然。Subject中文翻譯:項目,而正確的理解也恰恰如此。它是你目前所設計的需要通過Shiro保護的項目的一個抽象概念。通過令牌(token)與項目(subject)的登陸(login)關系,Shiro保證了項目整體的安全。
3.3 Realm
概述:官方文檔定義:
Realms
The third and final core concept in Shiro is that of a Realm. A Realm acts as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data. That is, when it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an application.
In this sense a Realm is essentially a security-specific DAO: it encapsulates connection details for data sources and makes the associated data available to Shiro as needed. When configuring Shiro, you must specify at least one Realm to use for authentication and/or authorization. More than one Realm may be configured, but at least one is required.
Shiro provides out-of-the-box Realms to connect to a number of security data sources (aka directories) such as LDAP, relational databases (JDBC), text configuration sources like INI and properties files, and more. You can plug-in your own Realm implementations to represent custom data sources if the default Realms do not meet your needs. Listing 4 below is an example of configuring Shiro (via INI) to use an LDAP directory as one of the application’s Realms.
大意是指:Realm充當?shù)氖荢hiro和應用程序安全數(shù)據(jù)之間的“橋梁”或“連接器”。也就是說,當實際需要與與安全相關的數(shù)據(jù)(如用戶帳戶)進行交互以執(zhí)行身份驗證(登錄)和授權(訪問控制)時,Shiro會從一個或多個為應用程序配置的Realm中查找這些內容。也是說Realm本質上是一個特定于安全性的DAO(邏輯處理):它封裝了數(shù)據(jù)源的連接細節(jié),并根據(jù)需要將關聯(lián)的數(shù)據(jù)提供給Shiro。在配置Shiro時,必須指定至少一個用于身份驗證和/或授權的領域??梢耘渲枚鄠€域,但至少需要一個。
Shiro提供了開箱即用的領域,可以連接到許多安全數(shù)據(jù)源(即目錄),如LDAP、關系數(shù)據(jù)庫(JDBC)、文本配置源(如INI)和屬性文件,等等。也可以自定義Realm實現(xiàn)來表示自定義數(shù)據(jù)源。
shiro結構圖:

獲取Subject:
/* ShiroUtils其實就是自定義對SecurityUtils做進一步優(yōu)化 */
/* 獲取項目管理信息 */
Subject subject = ShiroUtils.getSubject ();
調用login方法:
UsernamePasswordToken token = new UsernamePasswordToken ( username, password );
subject.login ( token );
內部調用的是subject接口聲明的方法:
void login(AuthenticationToken token) throws AuthenticationException;
login()實現(xiàn)源碼:
實現(xiàn)類:
public class DelegatingSubject implements Subject {
實現(xiàn)方法:
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;
//we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
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;
}
}
其內部調用的方法是:
Subject subject = securityManager.login(this, token);
對securityManager.login(this,token);源碼進行跟蹤:
Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
實現(xiàn)類:
public class DefaultSecurityManager extends SessionsSecurityManager {
實現(xiàn)方法:
/**
* First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a
* {@code Subject} instance representing the authenticated account's identity.
* <p/>
* Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for
* subsequent access before being returned to the caller.
*
* @param token the authenticationToken to process for the login attempt.
* @return a Subject representing the authenticated user.
* @throws AuthenticationException if there is a problem authenticating the specified {@code token}.
*/
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; //propagate
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
其中內部調用了 AuthenticationInfo info = authenticate(token);
SecurityManager接口中它所繼承的類:
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
從這里我們可以發(fā)現(xiàn)SecurityManager繼承了 登錄認證的接口比如登錄(Authenticator),權限驗證(Authorizer)等。
其中Authenticator中定義了 authenticate認證方法:
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
}
即DefaultSecurityManager接口中對login實現(xiàn)調用的方法:
/**
* First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a
* {@code Subject} instance representing the authenticated account's identity.
* <p/>
* Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for
* subsequent access before being returned to the caller.
*
* @param token the authenticationToken to process for the login attempt.
* @return a Subject representing the authenticated user.
* @throws AuthenticationException if there is a problem authenticating the specified {@code token}.
*/
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; //propagate
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
AbstractAuthenticator中authenticate()的實現(xiàn):
/**
* Implementation of the {@link Authenticator} interface that functions in the following manner:
* <ol>
* <li>Calls template {@link #doAuthenticate doAuthenticate} method for subclass execution of the actual
* authentication behavior.</li>
* <li>If an {@code AuthenticationException} is thrown during {@code doAuthenticate},
* {@link #notifyFailure(AuthenticationToken, AuthenticationException) notify} any registered
* {@link AuthenticationListener AuthenticationListener}s of the exception and then propagate the exception
* for the caller to handle.</li>
* <li>If no exception is thrown (indicating a successful login),
* {@link #notifySuccess(AuthenticationToken, AuthenticationInfo) notify} any registered
* {@link AuthenticationListener AuthenticationListener}s of the successful attempt.</li>
* <li>Return the {@code AuthenticationInfo}</li>
* </ol>
*
* @param token the submitted token representing the subject's (user's) login principals and credentials.
* @return the AuthenticationInfo referencing the authenticated user's account data.
* @throws AuthenticationException if there is any problem during the authentication process - see the
* interface's JavaDoc for a more detailed explanation.
*/
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) {
//Exception thrown was not an expected AuthenticationException. Therefore it is probably a little more
//severe or unexpected. So, wrap in an AuthenticationException, log to warn, and propagate:
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;
}
調用了doAuthenticate(token)方法
/**
* Template design pattern hook for subclasses to implement specific authentication behavior.
* <p/>
* Common behavior for most authentication attempts is encapsulated in the
* {@link #authenticate} method and that method invokes this one for custom behavior.
* <p/>
* <b>N.B.</b> Subclasses <em>should</em> throw some kind of
* {@code AuthenticationException} if there is a problem during
* authentication instead of returning {@code null}. A {@code null} return value indicates
* a configuration or programming error, since {@code AuthenticationException}s should
* indicate any expected problem (such as an unknown account or username, or invalid password, etc).
*
* @param token the authentication token encapsulating the user's login information.
* @return an {@code AuthenticationInfo} object encapsulating the user's account information
* important to Shiro.
* @throws AuthenticationException if there is a problem logging in the user.
*/
protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
throws AuthenticationException;
我們再來看ModularRealmAuthenticator中doAuthenticate(token)方法的實現(xiàn):
/**
* Attempts to authenticate the given token by iterating over the internal collection of
* {@link Realm}s. For each realm, first the {@link Realm#supports(org.apache.shiro.authc.AuthenticationToken)}
* method will be called to determine if the realm supports the {@code authenticationToken} method argument.
* <p/>
* If a realm does support
* the token, its {@link Realm#getAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)}
* method will be called. If the realm returns a non-null account, the token will be
* considered authenticated for that realm and the account data recorded. If the realm returns {@code null},
* the next realm will be consulted. If no realms support the token or all supporting realms return null,
* an {@link AuthenticationException} will be thrown to indicate that the user could not be authenticated.
* <p/>
* After all realms have been consulted, the information from each realm is aggregated into a single
* {@link AuthenticationInfo} object and returned.
*
* @param authenticationToken the token containing the authentication principal and credentials for the
* user being authenticated.
* @return account information attributed to the authenticated user.
* @throws IllegalStateException if no realms have been configured at the time this method is invoked
* @throws AuthenticationException if the user could not be authenticated or the user is denied authentication
* for the given principal and credentials.
*/
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
調用了doSingleRealmAuthentication(realms.iterator().next(), authenticationToken)
再往下看:doSingleRealmAuthentication的實現(xiàn):
/**
* Performs the authentication attempt by interacting with the single configured realm, which is significantly
* simpler than performing multi-realm logic.
*
* @param realm the realm to consult for AuthenticationInfo.
* @param token the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
* @return the AuthenticationInfo associated with the user account corresponding to the specified {@code token}
*/
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;
}
它調用Realm的getAuthenticationInfo(token)方法
而在Realm中我們看一下用戶認證方法重寫:
/**
*
* ClassName:com.code.modules.sys.shiro.UserRealm <br>
* Description:(shiro認證)<br>
* @author wangxiong <br>
* date 2020/4/8 15:21<br>
* @version v1.0 <br>
*/
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserDao sysUserDao;
@Autowired
private SysMenuDao sysMenuDao;
/**
* 授權(驗證權限時調用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal ();
Long userId = user.getUserId ();
List<String> permsList;
//系統(tǒng)管理員,擁有最高權限
if (userId == Constant.SUPER_ADMIN) {
/* 查詢所有菜單 */
List<SysMenuEntity> menuList = sysMenuDao.selectList ( null );
permsList = new ArrayList<> ( menuList.size () );
for (SysMenuEntity menu : menuList) {
/* 添加權限 */
permsList.add ( menu.getPerms () );
}
} else {
permsList = sysUserDao.queryAllPerms ( userId ); //關聯(lián)查詢 user - role - menu
}
//用戶權限列表
Set<String> permsSet = new HashSet<> ();
for (String perms : permsList) {
if (StringUtils.isBlank ( perms )) {
continue;
}
/* 查詢以逗號分隔的所有授權信息 */
permsSet.addAll ( Arrays.asList ( perms.trim ().split ( "," ) ) );
}
/* 授權 */
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo ();
info.setStringPermissions ( permsSet );
return info;
}
/**
* 認證(登錄時調用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
//查詢用戶信息
SysUserEntity user = sysUserDao.selectOne ( new QueryWrapper<SysUserEntity> ().eq ( "username", token.getUsername () ) );
//賬號不存在
if (user == null) {
throw new UnknownAccountException ( ExceptionEnum.ACCOUNT_OR_PASSWORD_IS_INCORRECT.getMsg () );
}
//賬號鎖定
if (user.getStatus () == 0) {
throw new LockedAccountException ( ExceptionEnum.ACCOUNT_IS_LOCKED.getMsg () );
}
//授權
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo ( user, user.getPassword (), ByteSource.Util.bytes ( user.getSalt () ), getName () );
return info;
}
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher ();
shaCredentialsMatcher.setHashAlgorithmName ( ShiroUtils.hashAlgorithmName );
shaCredentialsMatcher.setHashIterations ( ShiroUtils.hashIterations );
super.setCredentialsMatcher ( shaCredentialsMatcher );
}
}
主要重寫了倆個方法:
doGetAuthenticationInfo()主要是進行登錄認證
doGetAuthorizationInfo()主要是進行角色權限和對應權限的添加
Shiro 配置
要配置的是ShiroConfig類,Apache Shiro 核心通過 Filter 來實現(xiàn)(類似SpringMvc 通過DispachServlet 來主控制一樣)
filter主要是通過URL規(guī)則來進行過濾和權限校驗,所以我們需要定義一系列關于URL的規(guī)則和訪問權限。如下:
/**
*
* ClassName:com.code.common.config.ShiroConfig <br>
* Description:(Shiro的配置文件)<br>
* @author wangxiong <br>
* date 2020/4/8 14:25<br>
* @version v1.0 <br>
*/
@Configuration
public class ShiroConfig {
/**
* 單機環(huán)境,session交給shiro管理
*/
@Bean
@ConditionalOnProperty(prefix = "springboot_template", name = "cluster", havingValue = "false")
public DefaultWebSessionManager sessionManager(@Value("${springboot_template.globalSessionTimeout:3600}") long globalSessionTimeout) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager ();
/* 開啟session會話任務調度效驗 */
sessionManager.setSessionValidationSchedulerEnabled ( true );
/* 去掉shiro登錄時url里的JSESSIONID */
sessionManager.setSessionIdUrlRewritingEnabled ( false );
/* 會話驗證間隔 */
sessionManager.setSessionValidationInterval ( globalSessionTimeout * 1000 );
/* 會話超時 */
sessionManager.setGlobalSessionTimeout ( globalSessionTimeout * 1000 );
return sessionManager;
}
/**
* 集群環(huán)境,session交給spring-session管理
*/
@Bean
@ConditionalOnProperty(prefix = "springboot_template", name = "cluster", havingValue = "true")
public ServletContainerSessionManager servletContainerSessionManager() {
return new ServletContainerSessionManager ();
}
//權限管理,配置主要是Realm的管理認證
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager ();
securityManager.setRealm ( userRealm );
securityManager.setSessionManager ( sessionManager );
securityManager.setRememberMeManager ( null );
return securityManager;
}
/**
* Title: shiroFilter<br>
* Author: Man<br>
* Description: (權限認證過濾)<br>
* Date: 10:14 <br>
*
* @param securityManager return: org.apache.shiro.spring.web.ShiroFilterFactoryBean
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean ();
shiroFilter.setSecurityManager ( securityManager );
shiroFilter.setLoginUrl ( "/login.html" );
shiroFilter.setUnauthorizedUrl ( "/" );
Map<String, String> filterMap = new LinkedHashMap<> ();
/* anon 不做驗證;authc 要做驗證 */
filterMap.put ( "/swagger/**", "anon" );
filterMap.put ( "/v2/api-docs", "anon" );
filterMap.put ( "/swagger-ui.html", "anon" );
filterMap.put ( "/webjars/**", "anon" );
filterMap.put ( "/swagger-resources/**", "anon" );
filterMap.put ( "/statics/**", "anon" );
filterMap.put ( "/login.html", "anon" );
filterMap.put ( "/sys/login", "anon" );
filterMap.put ( "/favicon.ico", "anon" );
filterMap.put ( "/captcha.jpg", "anon" );
filterMap.put ( "/**", "authc" );
shiroFilter.setFilterChainDefinitionMap ( filterMap );
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor ();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor ();
advisor.setSecurityManager ( securityManager );
return advisor;
}
}
方法一與方法二是配置單機與集群管理。
方法三: 權限管理,配置主要是Realm的管理認證。
方法四: Filter工廠,設置對應的過濾條件和跳轉條件。
異常捕獲
在登錄過程中可能會出現(xiàn)不同的異常,對于不同的異常,我們是如何處理的呢?
當然不同的異常就要分類進行處理,比如密碼錯誤和賬戶不存在就不能一概而論,對于這些問題,我們能做的就是將不同的異常進行捕獲進行不同頁面的跳轉反饋給用戶,提高用戶體驗,比如:
try {
/* ShiroUtils其實就是自定義對SecurityUtils做進一步優(yōu)化 */
/* 獲取項目管理信息 */
Subject subject = ShiroUtils.getSubject ();
UsernamePasswordToken token = new UsernamePasswordToken ( username, password );
subject.login ( token );
} catch (UnknownAccountException e) {
return R.error ( e.getMessage () );
} catch (IncorrectCredentialsException e) {
return R.error ( "賬號或密碼不正確" );
} catch (LockedAccountException e) {
return R.error ( "賬號已被鎖定,請聯(lián)系管理員" );
} catch (AuthenticationException e) {
return R.error ( "賬戶驗證失敗" );
}