SpringSecurity使用记录(五)-- 配置

一直通过默认配置进行设置:
namespace(是security 3.0,网上也看到一些兄弟描述的是3.0,但是总是不符合我这里的namespace配置):
<beans:beans xmlns="http://www.springframework.org/schema/security"
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.0.xsd">
按照默认配置的http(这是用来根据namespace设置的基本的security过滤器chain):
auto-config=true时,就相当于
<http>
<form-login />
<http-basic />
<logout />
</http>
也就是使用了默认的过滤器。
我最开始的想法是能够把本地的login信息(不是调用spring security的login方法),传入到spring security的验证过滤器里面。
这里有一个比较关键的问题,就是封装他们的过滤器(或者仅仅是知道他们到底是哪些过滤器在起作用):

[table]
|Alias| Filter Class |Namespace Element or Attribute|
|CHANNEL_FILTER |ChannelProcessingFilter |http/intercept-url@requires-channel|
|CONCURRENT_SESSION_FILTER| ConcurrentSessionFilter |session-management/concurrency-control|
|SECURITY_CONTEXT_FILTER| SecurityContextPersistenceFilter|http|
|LOGOUT_FILTER|LogoutFilter|http/logout|
|X509_FILTER|X509AuthenticationFilter|http/x509|
|PRE_AUTH_FILTER|AstractPreAuthenticatedProcessingFilter Subclasses|N/A|
|CAS_FILTER|CasAuthenticationFilter|N/A|
|FORM_LOGIN_FILTER|UsernamePasswordAuthenticationFilter|http/form-login|
|BASIC_AUTH_FILTER|BasicAuthenticationFilter|http/http-basic|
|SERVLET_API_SUPPORT_FILTER|SecurityContextHolderAwareFilter|http/@servlet-api-provision|
|REMEMBER_ME_FILTER|RememberMeAuthenticationFilter|http/remember-me|
|ANONYMOUS_FILTER|AnonymousAuthenticationFilter|http/anonymous|
|SESSION_MANAGEMENT_FILTER|SessionManagementFilter|session-management|
|EXCEPTION_TRANSLATION_FILTER|ExceptionTranslationFilter|http|
|FILTER_SECURITY_INTERCEPTOR|FilterSecurityInterceptor|http|
|SWITCH_USER_FILTER|SwitchUserFilter|N/A|
[/table]

如果我们要自己定制相应的验证处理方法(在过滤器里面),我们就可以对照上面的过滤器,覆盖相应的接口方法。
根据我的处理过程,主要是用户名密码的验证过程,我大体描述一下自己的配置和处理过程:
1.配置namespace的标签:
  <http  use-expressions="true" ><!-- This is not the default value -->
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter"/> <!--This is my own filter which just extends AbstractAuthenticationProcessingFilter as what UsernamePasswordAuthenticationFilter does.-->
<intercept-url pattern="/test/**" access="hasRole('ROLE_MY')"/><!-- I tested that what is role,and how I can use it.So ROLE_MY is just a role name I defined.-->
<intercept-url pattern="/login.jsp*" access="permitAll" />
<logout />
<anonymous />
<http-basic />
</http>

这里的问题是,要定制自己的过滤器,就要通过<custom-filter/>,然后对照 表1 中指定的position,覆盖默认的filter。
2.我的form-login filter配置(这些配置都是在application-security.xml文件中)为:
<beans:bean id="myFilter" 
class="com.saveworld.authentication.filters.MyUsernamePasswordAuthenticationFilter">
<beans:property name="defaultTargetUrl" value="/default.jsp" />
<beans:property name="defaultFailureUrl" value="/error.jsp" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
<beans:property name="continueChainBeforeSuccessfulAuthentication" value="false" />
</beans:bean>


NOTE:
在这里有个问题就是: filter position conflicts!
如果使用这样的配置
<http auto-config='true'>
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter"/>
</http>

自定义的filter对应的position是FORM_LOGIN_FILTER
但是因为使用了auto-config='true',所以默认有<form-login />,which is on the position FORM_LOGIN_FILTER!
这时就会出现position conflicts问题了。当然,如果你没有设置auto-config='true',但是却自己设置了<form-login />,呵呵,这个情况就是自己大意了,还是有了position conflicts的异常,所以,好好看看上面的表格是相当必要的,看清楚每个position默认都对应那些namespace,都是对应的哪些filter!

接着:
我的类MyUsernamePasswordAuthenticationFilter实现(我的说明方式就按照哪里需要,哪里加入的方式了):
package com.saveworld.authentication.filters;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.NullRememberMeServices;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.TextEscapeUtils;
import org.springframework.util.Assert;

import com.saveworld.authentication.handlers.MySavedRequestAwareAuthenticationSuccessHandler;
import com.saveworld.authentication.handlers.MySimpleUrlAuthenticationFailureHandler;

public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter{
//~ Static fields/initializers =====================================================================================

public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME";

private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;

private boolean allowSessionCreation = true;

private String defaultTargetUrl = "/";
private String defaultFailureUrl = "/login.jsp";

private AuthenticationSuccessHandler successHandler = null;
private AuthenticationFailureHandler failureHandler = null;


private RememberMeServices rememberMeServices = null;

//~ Constructors ===================================================================================================

public MyUsernamePasswordAuthenticationFilter() {
//初始化
super("/j_spring_security_check");
this.rememberMeServices = (super.getRememberMeServices() == null)
? new NullRememberMeServices():super.getRememberMeServices();

}

//~ Methods ========================================================================================================

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}

String username = obtainUsername(request);
String password = obtainPassword(request);

if (username == null) {
username = "";
}

if (password == null) {
password = "";
}

username = username.trim();

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

// Place the last username attempted into HttpSession for views
HttpSession session = request.getSession(false);

if (session != null || getAllowSessionCreation()) {
request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));
}

// Allow subclasses to set the "details" property
setDetails(request, authRequest);

return this.getAuthenticationManager().authenticate(authRequest);
}



public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {

if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}

SecurityContextHolder.getContext().setAuthentication(authResult);

rememberMeServices.loginSuccess(request, response, authResult);

// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
if(successHandler == null){
successHandler = new MySavedRequestAwareAuthenticationSuccessHandler(getDefaultTargetUrl());
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}

public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();

if(failureHandler == null){
failureHandler = new MySimpleUrlAuthenticationFailureHandler(getDefaultFailureUrl());
}

if (logger.isDebugEnabled()) {
logger.debug("Authentication request failed: " + failed.toString());
logger.debug("Updated SecurityContextHolder to contain null Authentication");
logger.debug("Delegating to authentication failure handler" + failureHandler);
}

HttpSession session = request.getSession(false);

if (session != null || allowSessionCreation) {
request.getSession().setAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
}

rememberMeServices.loginFail(request, response);

failureHandler.onAuthenticationFailure(request, response, failed);
}


/**
* Enables subclasses to override the composition of the password, such as by including additional values
* and a separator.<p>This might be used for example if a postcode/zipcode was required in addition to the
* password. A delimiter such as a pipe (|) should be used to separate the password and extended value(s). The
* <code>AuthenticationDao</code> will need to generate the expected password in a corresponding manner.</p>
*
* @param request so that request attributes can be retrieved
*
* @return the password that will be presented in the <code>Authentication</code> request token to the
* <code>AuthenticationManager</code>
*/
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}

/**
* Enables subclasses to override the composition of the username, such as by including additional values
* and a separator.
*
* @param request so that request attributes can be retrieved
*
* @return the username that will be presented in the <code>Authentication</code> request token to the
* <code>AuthenticationManager</code>
*/
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}

/**
* Provided so that subclasses may configure what is put into the authentication request's details
* property.
*
* @param request that an authentication request is being created for
* @param authRequest the authentication request object that should have its details set
*/
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}

/**
* Sets the parameter name which will be used to obtain the username from the login request.
*
* @param usernameParameter the parameter name. Defaults to "j_username".
*/
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}

/**
* Sets the parameter name which will be used to obtain the password from the login request..
*
* @param passwordParameter the parameter name. Defaults to "j_password".
*/
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}

/**
* Defines whether only HTTP POST requests will be allowed by this filter.
* If set to true, and an authentication request is received which is not a POST request, an exception will
* be raised immediately and authentication will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
* will be called as if handling a failed authentication.
* <p>
* Defaults to <tt>true</tt> but may be overridden by subclasses.
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}

public final String getUsernameParameter() {
return usernameParameter;
}

public final String getPasswordParameter() {
return passwordParameter;
}

public String getDefaultTargetUrl() {
return defaultTargetUrl;
}

public void setDefaultTargetUrl(String defaultTargetUrl) {
this.defaultTargetUrl = defaultTargetUrl;
}

public String getDefaultFailureUrl() {
return defaultFailureUrl;
}

public void setDefaultFailureUrl(String defaultFailureUrl) {
this.defaultFailureUrl = defaultFailureUrl;
}


}


这里要关注的就是几个字段:
<beans:property name="defaultTargetUrl" value="/default.jsp" />
<beans:property name="defaultFailureUrl" value="/error.jsp" />
这两个字段是指定验证成功或失败后转向的页面,这里要注意是以“/”开头,否则在AbstractAuthenticationTargetUrlRequestHandler中调用setDefaultTargetUrl方法时会抛出"defaultTarget must start with '/' or with 'http(s)'"的异常!
默认情况下,FORM_LOGIN_FILTER对应的target url和failure url都是通过 <form-login />中的[b]default-target-url,authentication-failure-url指定,也可以通过指定authentication-success-handler-ref和authentication-failure-handler-ref来实现认证成功和失败之后的处理方式.在我的filter中,是自定义了两个handler分别对应成功的和失败的验证。[/b]


3.用户信息获取和验证:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>
<beans:bean id="myUserDetailsService" class="com.saveworld.userdetails.MyUserDetailsService"></beans:bean>
这个指定的authentication-manager是使用默认的ProviderManager,这个manager是在哪里使用的呢?
看看MyUsernamePasswordAuthenticationFilter中的attemptAuthentication方法的最后一行,这里是获取指定的authentication-manager。getAuthenticationManager是从父类AbstractAuthenticationProcessingFilter继承过来的。所以,我们的<authentication-manager alias="authenticationManager">中就指定了一个别名authenticationManager,在myfilter中设置属性的引用<beans:property name="authenticationManager" ref="authenticationManager" />,然后我们就可以通过Provider引用我们自己的用户信息验证service了(eg:用户信息获取和验证)!这里实际是使用了Method Template模式(AbstractAuthenticationManager中设定模板函数doAuthentication,ProviderManager中做了实现)。
这里难免要说明一下,我们的Service是如何被调用的,我们做了配置<authentication-provider user-service-ref='myUserDetailsService'/>
指定了我们的UserDetailsService,类实现(为了测试和理解,The easier the better!):
package com.saveworld.userdetails;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class MyUserDetailsService implements UserDetailsService{

private HashMap<String, User> userDetails = new HashMap<String, User>();
private HashMap<String, List<GrantedAuthority>> userAuthorities = new HashMap<String, List<GrantedAuthority>>();

public MyUserDetailsService(){
//Make up a user named 'rod' with 'rod' as his password!
//
String username = "rod";
String password = "1";
boolean enabled = true;
//purview for rod
GrantedAuthority specAuth = new GrantedAuthorityImpl("ROLE_MY");
List<GrantedAuthority> rodsAuthsList = new ArrayList<GrantedAuthority>();
rodsAuthsList.add(specAuth);
// userAuthorities.put("rod", rodsAuthsList);
userDetails.put("rod", new User(username, password, enabled, true, true, true, rodsAuthsList));
}


public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
System.out.println("验证从此地过了一遭");
return userDetails.get(username);
}

}

通过DaoAuthenticationProvider中的userDetailsService关联我们的UserDetailsService(不得不提的是,AbstractUserDetailsAuthenticationProvider中有设定了模板函数retrieveUser,DaoAuthenticationProvider进行了实现,通过retrieveUser方法调用UserDetailsService.loadUserByUsername,然后在AbstractUserDetailsAuthenticationProvider.authenticate方法进行验证)。
接下来就是看验证的结果了,是否成功,进入filter chain中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值