Shiro框架 filter&realm綁定
這種實現(xiàn)方式有問題葵诈,會影響到后續(xù)的角色驗證。不建議看了
新的實現(xiàn)方式 : 自定義Token
- shiro filter與Realm綁定
- 使用Spring整合Shiro
- 多登陸入口劲适,成功后跳轉到不同地址
- 重寫Realm獲取方式
最近在項目中使用了apache的輕量級Shiro框架進行權限管理楷掉,使用了多個filter,Shiro會默認迭代Realm進行登陸减响,即使第一個Realm通過校驗靖诗,也會繼續(xù)迭代,造成性能的浪費支示,和數(shù)據(jù)庫查詢刊橘。又或者前Realm里做了一些校驗,拋出了異常颂鸿,也會被后面的無用的Realm校驗失敗拋出的一場覆蓋促绵。
不廢話,上教程不廢話嘴纺,上教程
注意:這里filter統(tǒng)一指shiro 定義的filter
至于servlet的filter會寫成 servlet filter
這是先前的配置
已經(jīng)實現(xiàn)方式賦予Realm對應的role角色败晴,已經(jīng)有多入口,前后臺分開的功能栽渴,
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- shiro 的核心安全接口 -->
<property name="securityManager" ref="securityManager" />
<!-- 未授權時要跳轉的連接 -->
<property name="unauthorizedUrl" value="/unauthorized.jsp" />
<property name="filters">
<map>
<entry key="admin" value-ref="adminformAuthenticationFilter" />
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<!-- shiro 連接約束配置 -->
<property name="filterChainDefinitions">
<value>
/user.html = authc
/admin.html =authc
/login.html = authc
/admin/login.html =admin
/logout = logout
/resource/** = anon
</value>
</property>
</bean>
<bean id="formAuthenticationFilter"
class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="loginUrl" value="/login.html" />
<property name="successUrl" value="/ok.html" />
</bean>
<bean id="adminformAuthenticationFilter"
class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="loginUrl" value="/admin/login.html" />
<property name="successUrl" value="/admin/ok.html" />
</bean>
<bean id="AdminRealm" class="cc.yihy.realm.AdminRealm">
<property name="userService" ref="UserService" />
</bean>
<bean id="UserRealm" class="cc.yihy.realm.UserRealm">
<property name="userService" ref="UserService" />
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 默認的Realm驗證 -->
<property name="realms">
<list>
<ref bean="UserRealm" />
<ref bean="AdminRealm"></ref>
</list>
</property>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
在我們點擊登陸后尖坤,Shiro會使用我們配置的FormAuthenticationFilter進行驗證。
執(zhí)行
//父類里實現(xiàn)的方法
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
//這里創(chuàng)建用戶的令牌
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);
}
}
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
return createToken(username, password, request, response);
}
//父類的創(chuàng)建令牌的實現(xiàn)
protected AuthenticationToken createToken(String username, String password,
ServletRequest request, ServletResponse response) {
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return createToken(username, password, rememberMe, host);
}
//父類的創(chuàng)建令牌的實現(xiàn)
protected AuthenticationToken createToken(String username, String password,
boolean rememberMe, String host) {
return new UsernamePasswordToken(username, password, rememberMe, host);
}
subject.login(token);的實現(xiàn)類里闲擦,又調(diào)用了安全管理器的login慢味。傳的對象是一個Token令牌。
再看看安全管理器里面是怎么進行登錄的
Subject subject = securityManager.login(this, token);
//安全管理器實現(xiàn)類DefaultSecurityManager里面的login
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;
}
//這是它的父類里面
private Authenticator authenticator;
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
//真正進行登陸Realm的在這里
return this.authenticator.authenticate(token);
}
還是看下Authenticator接口實現(xiàn)類(ModularRealmAuthenticator)的源碼
這是配置的Realm集合
private Collection<Realm> realms;
//迭代Realm進行驗證
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);
}
}
到這里墅冷,filter調(diào)用realm進行驗證的流程基本已經(jīng)清晰了纯路。
filter創(chuàng)建一個Token對象,傳到安全管理器(securityManager)寞忿,讓安全管理器調(diào)用Authenticator屬性進行迭代Realm驰唬。
怎么讓Filter找到對應權限的realm呢?他們中間傳遞一一個對象Token令牌腔彰。
Token里放了這些基本信息
new UsernamePasswordToken(username, password, rememberMe, host)叫编;
從filter到realm并沒有角色名的傳遞。
filter的name屬性里放的是角色名霹抛。這點在Spring創(chuàng)建ShiroFilter 可以看到宵溅。
//getFilters()拿到的是xml配置上的filters
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
//'init' argument is false, since Spring-configured filters should be initialized
//in Spring (i.e. 'init-method=blah') or implement InitializingBean:
manager.addFilter(name, filter, false);
}
}
現(xiàn)在想要把filter的信息傳給realm就需要在他們之間傳遞的對象動手腳了。讓Token把Filter對象帶過去就行了上炎。
那那邊怎么處理呢。
實例安全管理器的時候,會實例一個ModularRealmAuthorizer對象藕施,并可以設置為我們自定義的寇损。
private Authorizer authorizer;
//安全管理器實現(xiàn)類的父類,在實例安全管理器的時候裳食,會創(chuàng)建一個ModularRealmAuthorizer對象矛市,迭代Realm驗證,就是ModularRealmAuthorizer做的工作
/**
* Default no-arg constructor that initializes an internal default
* {@link org.apache.shiro.authz.ModularRealmAuthorizer ModularRealmAuthorizer}.
*/
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}
public void setAuthorizer(Authorizer authorizer) {
if (authorizer == null) {
String msg = "Authorizer argument cannot be null.";
throw new IllegalArgumentException(msg);
}
this.authorizer = authorizer;
}
到這里诲祸,F(xiàn)ilter&Realm綁定需要修改的地方基本都知道了浊吏。
重寫FormAuthenticationFilter的createToken(String username, String password,boolean rememberMe, String host)方法,自定義一個Token救氯,可以存放Filter找田。重寫ModularRealmAuthorizer對象中獲取Realm的方法。
下面是實現(xiàn)代碼
NewFormAuthenticationFilter 實現(xiàn)
public class NewFormAuthenticationFilter extends FormAuthenticationFilter {
/**
* 創(chuàng)建自定義的令牌着憨,加入當前filter
*/
@Override
protected AuthenticationToken createToken(String username, String password,
boolean rememberMe, String host) {
return new UsernamePasswordAndFilterToken(username, password,
rememberMe, host, this);
}
/**
* 獲取當前Filter的名字(角色名)擴大訪問范圍
*/
@Override
public String getName() {
return super.getName();
}
}
UsernamePasswordAndFilterToken 實現(xiàn)
public class UsernamePasswordAndFilterToken extends UsernamePasswordToken {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 存放當前Filter
*/
private NameableFilter loginFilter;
public UsernamePasswordAndFilterToken() {
super();
}
public UsernamePasswordAndFilterToken(final String username,
final char[] password, final boolean rememberMe, final String host,
final AdviceFilter loginFilter) {
super(username, password, rememberMe, host);
this.loginFilter = loginFilter;
}
public UsernamePasswordAndFilterToken(final String username,
final String password, final boolean rememberMe, final String host,
final AdviceFilter loginFilter) {
super(username, password, rememberMe, host);
this.loginFilter = loginFilter;
}
public NameableFilter getLoginFilter() {
return loginFilter;
}
public void setLoginFilter(NameableFilter loginFilter) {
this.loginFilter = loginFilter;
}
}
DefineModularRealmAuthenticator 實現(xiàn)
public class DefineModularRealmAuthenticator extends ModularRealmAuthenticator {
private static final Logger log = LoggerFactory
.getLogger(DefineModularRealmAuthenticator.class);
private Map<String, Realm> defineRealms;
/**
* 判斷Realm是不是null
*/
@Override
protected void assertRealmsConfigured() throws IllegalStateException {
defineRealms = getDefineRealms();
if (CollectionUtils.isEmpty(defineRealms)) {
String msg = "Configuration error: No realms have been configured! One or more realms must be "
+ "present to execute an authentication attempt.";
throw new IllegalStateException(msg);
}
}
/**
* 根據(jù)filter的name 取對應realm進行登錄
*/
@Override
protected AuthenticationInfo doAuthenticate(
AuthenticationToken authenticationToken)
throws AuthenticationException {
assertRealmsConfigured();
/**
* authenticationToken 如果是自定義的UsernamePasswordAndFilterToken墩衙,調(diào)用單個realm
* 否則 使用默認的迭代realm方式
*/
if(authenticationToken instanceof UsernamePasswordAndFilterToken){
NewFormAuthenticationFilter loginFilter = (NewFormAuthenticationFilter)((UsernamePasswordAndFilterToken) authenticationToken).getLoginFilter();
Realm realm = defineRealms.get(loginFilter.getName());
if(realm==null){
log.error("沒有配置NewFormAuthenticationFilter對應的Realm");
throw new RuntimeException("沒有配置NewFormAuthenticationFilter對應的Realm");
};
return doSingleRealmAuthentication(realm,authenticationToken);
}else {
return oldDoAuthenticate(authenticationToken);
}
}
private AuthenticationInfo oldDoAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException{
Collection<Realm> realms = defineRealms.values();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
public void setDefineRealms(Map<String, Realm> defineRealms) {
this.defineRealms = defineRealms;
}
public Map<String, Realm> getDefineRealms() {
return defineRealms;
}
}
在xml中的配置修改為
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- shiro 的核心安全接口 -->
<property name="securityManager" ref="securityManager" />
<!-- 未授權時要跳轉的連接 -->
<property name="unauthorizedUrl" value="/unauthorized.jsp" />
<property name="filters">
<map>
<entry key="admin" value-ref="adminformAuthenticationFilter" />
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<!-- shiro 連接約束配置 -->
<property name="filterChainDefinitions">
<value>
/user.html = authc
/admin.html =authc
/login.html = authc
/admin/login.html =admin
/logout = logout
/resource/** = anon
</value>
</property>
</bean>
<!-- filter&realm綁定 -->
<bean id="DefineModularRealmAuthenticator"
class="cc.yihy.shiro.authc.pam.DefineModularRealmAuthenticator">
<property name="defineRealms">
<map>
<entry key="authc" value-ref="UserRealm" />
<entry key="admin" value-ref="AdminRealm" />
</map>
</property>
</bean>
<bean id="formAuthenticationFilter"
class="cc.yihy.shiro.filter.NewFormAuthenticationFilter">
<property name="loginUrl" value="/login.html" />
<property name="successUrl" value="/ok.html" />
</bean>
<bean id="adminformAuthenticationFilter"
class="cc.yihy.shiro.filter.NewFormAuthenticationFilter">
<property name="loginUrl" value="/admin/login.html" />
<property name="successUrl" value="/admin/ok.html" />
</bean>
<bean id="AdminRealm" class="cc.yihy.realm.AdminRealm">
<property name="userService" ref="UserService" />
</bean>
<bean id="UserRealm" class="cc.yihy.realm.UserRealm">
<property name="userService" ref="UserService" />
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- filter&realm綁定 -->
<property name="authenticator" ref="DefineModularRealmAuthenticator" />
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
到此,綁定工作完成甲抖。漆改。。
歡迎拍磚提問准谚。
用MarkDown寫博客感覺還不錯挫剑。
表示差點把Shiro開始創(chuàng)建執(zhí)行的過程寫上去。
在后面進行頁面上權限校驗會報錯柱衔,原因出在改變了realm的注入方式樊破。解決辦法,近期我會整理出來秀存。
權限校驗問題修復
權限校驗會報錯是因為修改了注入realm的位置捶码,導致授權解釋器拿不到realm集合,現(xiàn)在把授權解釋器重寫了或链,并注入realms惫恼,使其恢復正常
<!-- realms Map -->
<bean id="defineRealms" class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="sourceMap">
<map>
<entry key="authc" value-ref="SSOUserRealm"/>
<!--<entry key="NormalUser" value-ref="UserRealm"/>-->
<!--<entry key="authUser" value-ref="UserRealm"/>-->
<entry key="SSOUser" value-ref="SSOUserRealm"/>
<entry key="mxUser" value-ref="AUserRealm"/>
<entry key="normalMx" value-ref="AUserRealm"/>
<entry key="mxAdmin" value-ref="AUserRealm"/>
<entry key="subMxAdmin" value-ref="AUserRealm"/>
<entry key="xhAdmin" value-ref="XhUserRealm"/>
<entry key="xhUser" value-ref="XhUserRealm"/>
<entry key="subXhUser" value-ref="XhUserRealm"/>
</map>
</property>
</bean>
<!-- filter&realm綁定 -->
<!--認證解釋器-->
<bean id="DefineModularRealmAuthenticator"
class="cc.yihy.shiro.authc.pam.DefineModularRealmAuthenticator">
<property name="defineRealms" ref="defineRealms"/>
</bean>
<!--授權解釋器-->
<bean id="DefineModularRealmAuthorizer"
class="cc.yihy.shiro.authz.DefineModularRealmAuthorizer">
<property name="defineRealms" ref="defineRealms"/>
</bean>
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- filter&realm綁定 登錄 -->
<property name="authenticator" ref="DefineModularRealmAuthenticator"/>
<!-- filter&realm綁定 角色權限驗證 -->
<property name="authorizer" ref="DefineModularRealmAuthorizer"/>
<!-- 注入緩存管理器 -->
<property name="cacheManager" ref="ShiroCacheManager"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager"/>
<!-- 記住我 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>