1、前言php
在上一篇http://blog.csdn.net/k10509806/archive/2011/04/28/6369131.aspx文章中,提到的MyUserDetailServiceImpl獲取用戶權限,在用戶沒有登錄的時候,Spring Security會讓咱們自動跳轉到默認的登錄界面,但在實際應用絕大多數是用咱們本身的登錄界面的,其中就包括一些咱們本身的邏輯,好比驗證碼。因此本人又研究一下,終於摸清了一些如何配置本身的登錄界面的辦法。在這裏獻醜了。html
2、Spring Security的過濾器java
經過DEBUG能夠看到Spring Security的Filter的順序web
Security filter chain: [
ConcurrentSessionFilter
SecurityContextPersistenceFilter
LogoutFilter
MyUsernamePasswordAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
MySecurityFilter
FilterSecurityInterceptor
]spring
Spring Security的登錄驗證用的就是MyUsernamePasswordAuthenticationFilter,因此要實現咱們本身的驗證,能夠寫一個類並繼承MyUsernamePasswordAuthenticationFilter類,重寫attemptAuthentication方法。express
3、applicationContext-Security.xml配置apache
[xhtml] view plaincopysession
<?xml version="1.0" encoding="UTF-8"?> app
<beans:beans xmlns="http://www.springframework.org/schema/security" jsp
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<debug/>
<http pattern="/js/**" security="none"/>
<http pattern="/resource/**" security="none"></http>
<http pattern="/login.jsp" security="none"/>
<http use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint">
<logout/>
<!-- 實現免登錄驗證 -->
<remember-me />
<session-management invalid-session-url="/timeout.jsp">
<concurrency-control max-sessions="10" error-if-maximum-exceeded="true" />
</session-management>
<custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER" />
<custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</http>
<!-- 登陸驗證器 -->
<beans:bean id="loginFilter"
class="com.huaxin.security.MyUsernamePasswordAuthenticationFilter">
<!-- 處理登陸的action -->
<beans:property name="filterProcessesUrl" value="/j_spring_security_check"></beans:property>
<!-- 驗證成功後的處理-->
<beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></beans:property>
<!-- 驗證失敗後的處理-->
<beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></beans:property>
<beans:property name="authenticationManager" ref="myAuthenticationManager"></beans:property>
<!-- 注入DAO爲了查詢相應的用戶 -->
<beans:property name="usersDao" ref="usersDao"></beans:property>
</beans:bean>
<beans:bean id="loginLogAuthenticationSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/index.jsp"></beans:property>
</beans:bean>
<beans:bean id="simpleUrlAuthenticationFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<!-- 能夠配置相應的跳轉方式。屬性forwardToDestination爲true採用forward false爲sendRedirect -->
<beans:property name="defaultFailureUrl" value="/login.jsp"></beans:property>
</beans:bean>
<!-- 認證過濾器 -->
<beans:bean id="securityFilter" class="com.huaxin.security.MySecurityFilter">
<!-- 用戶擁有的權限 -->
<beans:property name="authenticationManager" ref="myAuthenticationManager" />
<!-- 用戶是否擁有所請求資源的權限 -->
<beans:property name="accessDecisionManager" ref="myAccessDecisionManager" />
<!-- 資源與權限對應關係 -->
<beans:property name="securityMetadataSource" ref="mySecurityMetadataSource" />
</beans:bean>
<!-- 實現了UserDetailsService的Bean -->
<authentication-manager alias="myAuthenticationManager">
<authentication-provider user-service-ref="myUserDetailServiceImpl" />
</authentication-manager>
<beans:bean id="myAccessDecisionManager" class="com.huaxin.security.MyAccessDecisionManager"></beans:bean>
<beans:bean id="mySecurityMetadataSource" class="com.huaxin.security.MySecurityMetadataSource">
<beans:constructor-arg name="resourcesDao" ref="resourcesDao"></beans:constructor-arg>
</beans:bean>
<beans:bean id="myUserDetailServiceImpl" class="com.huaxin.security.MyUserDetailServiceImpl">
<beans:property name="usersDao" ref="usersDao"></beans:property>
</beans:bean>
<!-- 未登陸的切入點 -->
<beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login.jsp"></beans:property>
</beans:bean>
</beans:beans>
這裏特別要說明一下,咱們的<http>標籤不能配置auto-config,由於這樣配置後,依然會採用Spring Security的Filter Chain會與下面咱們配的custom-filter衝突,最好會拋異常。還有配置一個切入點entry-point-ref="authenticationProcessingFilterEntryPoint",爲了在未登錄的時候,跳轉到哪一個頁面,不配也會拋異常。
<custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER" /> position表示替換掉Spring Security原來默認的登錄驗證Filter。
4、MyUsernamePasswordAuthenticationFilter
[java] view plaincopy
package com.huaxin.security;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.xwork.StringUtils;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.huaxin.bean.Users;
import com.huaxin.dao.UsersDao;
/*
*
* UsernamePasswordAuthenticationFilter源碼
attemptAuthentication
this.getAuthenticationManager()
ProviderManager.java
authenticate(UsernamePasswordAuthenticationToken authRequest)
AbstractUserDetailsAuthenticationProvider.java
authenticate(Authentication authentication)
P155 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
DaoAuthenticationProvider.java
P86 loadUserByUsername
*/
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
public static final String VALIDATE_CODE = "validateCode";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
private UsersDao usersDao;
public UsersDao getUsersDao() {
return usersDao;
}
public void setUsersDao(UsersDao usersDao) {
this.usersDao = usersDao;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
//檢測驗證碼
checkValidateCode(request);
String username = obtainUsername(request);
String password = obtainPassword(request);
//驗證用戶帳號與密碼是否對應
username = username.trim();
Users users = this.usersDao.findByName(username);
if(users == null || !users.getPassword().equals(password)) {
/*
在咱們配置的simpleUrlAuthenticationFailureHandler處理登陸失敗的處理類在這麼一段
這樣咱們能夠在登陸失敗後,向用戶提供相應的信息。
if (forwardToDestination) {
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
} else {
HttpSession session = request.getSession(false);
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
}
}
*/
throw new AuthenticationServiceException("用戶名或者密碼錯誤!");
}
//UsernamePasswordAuthenticationToken實現 Authentication
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Place the last username attempted into HttpSession for views
// 容許子類設置詳細屬性
setDetails(request, authRequest);
// 運行UserDetailsService的loadUserByUsername 再次封裝Authentication
return this.getAuthenticationManager().authenticate(authRequest);
}
protected void checkValidateCode(HttpServletRequest request) {
HttpSession session = request.getSession();
String sessionValidateCode = obtainSessionValidateCode(session);
//讓上一次的驗證碼失效
session.setAttribute(VALIDATE_CODE, null);
String validateCodeParameter = obtainValidateCodeParameter(request);
if (StringUtils.isEmpty(validateCodeParameter) || !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {
throw new AuthenticationServiceException("驗證碼錯誤!");
}
}
private String obtainValidateCodeParameter(HttpServletRequest request) {
Object obj = request.getParameter(VALIDATE_CODE);
return null == obj ? "" : obj.toString();
}
protected String obtainSessionValidateCode(HttpSession session) {
Object obj = session.getAttribute(VALIDATE_CODE);
return null == obj ? "" : obj.toString();
}
@Override
protected String obtainUsername(HttpServletRequest request) {
Object obj = request.getParameter(USERNAME);
return null == obj ? "" : obj.toString();
}
@Override
protected String obtainPassword(HttpServletRequest request) {
Object obj = request.getParameter(PASSWORD);
return null == obj ? "" : obj.toString();
}
}
有時間,你們看看源碼吧。
5、login.jsp
[php] view plaincopy
<body>
<span style="color:red"><%=session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) %></span>
<form action="j_spring_security_check" method="post">
Account:<Input name="username"/><br/>
Password:<input name="password" type="password"/><br/>
<input value="submit" type="submit"/>
</form>
</body>