源碼下載下傳 我的GITHUB
shiro 相比與spring security配置上簡單很多。
加載shiro config
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:shiro-config.xml
</param-value>
</context-param>
添加shrio過濾器,這裡需要注意的是,過濾器的名字shiroFilter必須與配置檔案裡面的bean名字相同
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
shiroFilter配置,這裡需要配置一個 formAuthenticationFilter,作用在後面會提到,主要是因為extjs的ajax請求。
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- override these for application-specific URLs if you like:-->
<property name="loginUrl" value="/login.html"/>
<property name="successUrl" value="/index.html"/>
<property name="unauthorizedUrl" value="/unauthorized.html"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/comp/** = anon
/pages/** = anon
/resources/** = anon
/view/** = anon
/** = authc
</value>
</property>
</bean>
shiroFilter中引用了 securityManageer。如果參考shiro官網的同學請注意,官網documentation中的manager是org.apache.shiro.mgt.DefaultWebSecurityManager。在1.2.2中,這個類是沒有實作WebSecurityManager的,是以會跑出unimplement WebSecurityManager的異常。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. 如果有多個realm,需要用reamls參數,為實踐。-->
<property name="realm" ref="fastRealm"/>
</bean>
引用的realm是可以重寫的。
<bean id="fastRealm" class="com.chinadreamer.contacts.filter.shiro.UserRealm"></bean>
重寫realm。
重寫的doGetAuthorizationInfo與doGetAuthenticationInfo在官網中已經很明确的說明了,這裡不再解釋。
我說下這裡會遇到的一個問題。shiro跟spring的filter并沒有優先級之分,而使用者驗證,一般都會涉及到datasource bean的引用,比方這裡的userservice。是以需要注意的是必須在spring bean加載之後,進入shiro,否則是無法加載的。
我的做法是在applicationContext.xml 結尾才引入shiro-config.xml,這樣就可以擷取到bean了。
引入方式比較正常
<import resource="shiro-config.xml"/>
public class UserRealm extends AuthorizingRealm{
private final static Logger LOGGER = Logger.getLogger(UserRealm.class);
@Autowired
private UserService userService;
public UserRealm(){
super();
LOGGER.info(" init user realm ");
setAuthenticationTokenClass(UsernamePasswordToken.class);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
User user = ShiroUtils.getUser();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
User user;
try {
user = this.userService.userLogin(username, password);
} catch (Exception e) {
throw new AuthenticationException(e.getMessage(),e);
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password.toCharArray(), getName());
return info;
}
}
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
簡單的shiro到這就可以了。但是如果配合extjs,就需要控制ajax傳回,大家知道extj是根據response會寫的success 字段來判斷的。
<!--替換預設的form 驗證過濾器-->
<bean id="formAuthenticationFilter" class="com.chinadreamer.contacts.filter.shiro.FastFormAuthenticationFilter">
</bean>
提供一份源碼,根據各自的需求改動,這是其他大大提供的。
package com.chinadreamer.contacts.filter.shiro;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
public class FastFormAuthenticationFilter extends FormAuthenticationFilter{
private static final Logger LOGGER = Logger.getLogger(FastFormAuthenticationFilter.class);
/*
* 主要是針對登入成功的處理方法。對于請求頭是AJAX的之間傳回JSON字元串。
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token,
Subject subject, ServletRequest request, ServletResponse response)
throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (!"XMLHttpRequest".equalsIgnoreCase(httpServletRequest
.getHeader("X-Requested-With"))) {// 不是ajax請求
issueSuccessRedirect(request, response);
} else {
httpServletResponse.setCharacterEncoding("UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.println("{success:true,message:'登入成功'}");
out.flush();
out.close();
}
return false;
}
/**
* 主要是處理登入失敗的方法
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request,
ServletResponse response) {
if (!"XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request)
.getHeader("X-Requested-With"))) {// 不是ajax請求
setFailureAttribute(request, e);
return true;
}
try {
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String message = e.getClass().getSimpleName();
if ("IncorrectCredentialsException".equals(message)) {
out.println("{success:false,message:'密碼錯誤'}");
} else if ("UnknownAccountException".equals(message)) {
out.println("{success:false,message:'賬号不存在'}");
} else if ("LockedAccountException".equals(message)) {
out.println("{success:false,message:'賬号被鎖定'}");
} else {
out.println("{success:false,message:'未知錯誤'}");
}
out.flush();
out.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return false;
}
/**
* 所有請求都會經過的方法。
*/
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Login submission detected. Attempting to execute login.");
}
if ("XMLHttpRequest"
.equalsIgnoreCase(((HttpServletRequest) request)
.getHeader("X-Requested-With"))) {// 不是ajax請求
// String vcode = request.getParameter("vcode");
// HttpServletRequest httpservletrequest = (HttpServletRequest) request;
// String vvcode = (String) httpservletrequest
// .getSession()
// .getAttribute(
// com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
// if (vvcode == null || "".equals(vvcode)
// || !vvcode.equals(vcode)) {
// response.setCharacterEncoding("UTF-8");
// PrintWriter out = response.getWriter();
// out.println("{success:false,message:'驗證碼錯誤'}");
// out.flush();
// out.close();
// return false;
// }
}
return executeLogin(request, response);
} else {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Login page view.");
}
// allow them to see the login page ;)
return true;
}
} else {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Attempting to access a path which requires authentication. Forwarding to the "
+ "Authentication url [" + getLoginUrl() + "]");
}
if (!"XMLHttpRequest"
.equalsIgnoreCase(((HttpServletRequest) request)
.getHeader("X-Requested-With"))) {// 不是ajax請求
saveRequestAndRedirectToLogin(request, response);
} else {
// response.setCharacterEncoding("UTF-8");
// PrintWriter out = response.getWriter();
// out.println("{success:true,message:'login'}");
// out.flush();
// out.close();
return true;
}
return false;
}
}
}