Spring Security调研记录【二】--实现异步Json请求的基本认证与Url权限控制

在《Spring Security调研记录【一】--实现基本认证与Url权限控制》中,实现的页面的认证与授权(基于Url),但AJAX、Android等的异步Json请求也是非常常用功能。

本文章主要实现异步Json请求的登录、认证与授权

        一、首先明确与本实例相关的Spring  Security对象的作用

        Spring  Security是基于Filter实现,在Servlet前执行,所有一切为执行一系列的Filter。

        1、DelegatingFilterProxy

DelegatingFilterProxy提供一个web.xml与application context的连接,把web.xml请求连接至FilterChainProxy进行处理,FilterChainProxy在Spring Context进行定义,能最大化应用Spring配置优势。

        2、FilterChainProxy

FilterChainProxy中定义一系列按照顺序执行的Filter。具体的Filter请参看Spring Security Reference。与本实例相关的Filter:

UsernamePasswordAuthenticationFilter

RememberMeAuthenticationFilter

AnonymousAuthenticationFilter
ExceptionTranslationFilter

FilterSecurityInterceptor

每个Filter需要关联主要对象为AuthenticationManager、SecurityContextHolder、Authentication、AccessDecisionManager(本例暂不关注)。

3、UsernamePasswordAuthenticationFilter

本Filter仅有于登录。默认绑定登录的url为"/login"(可自定义),则request url为绑定值,才执行本Filter。登录成功则委托AuthenticationSuccessHandler执行,并生成Authentication,保存至SecurityContextHolder.getContext()中,执行后续Filter;如果失败则委托AuthenticationFailureHandler执行,清理session与cache,不执行后续Filter了。

UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter

        本例自定义一个类RestfulUsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter,实现UsernamePasswordAuthenticationFilter的类似功能,只是提供方便的request url设置和组装新的AuthenticationFailureHandler和AuthenticationSuccessHandler。

4、RememberMeAuthenticationFilter

本Filter紧跟UsernamePasswordAuthenticationFilter,用于进行登录后的基于token的认证,如果认证成功则生成Authentication,保存至SecurityContextHolder.getContext()中。
5、AnonymousAuthenticationFilter

         在UsernamePasswordAuthenticationFilter和RememberMeAuthenticationFilter中都不通过,未生成Authentication,AnonymousAuthenticationFilter则生成一个匿名用户的Authentication,并保存到securityContextHolder.getContext()中,为后续Filter提供Authentication。

6、ExceptionTranslationFilter

本Filter不做任何Authentication和Authorization的工作,只是解析Exception,做出相应的动作。本Filter先执行FilterSecurityInterceptor的过滤,捕捉异常和收集异常,如果为Authentication异常则委托AuthenticationEntryPoint去处理;如果为Authorization异常且用户不是匿名用户,则委托AccessDeniedHandler去处理;如果用户为匿名用户(Anonymous),且异常为Authorization异常,也委托AuthenticationEntryPoint去处理。

7、FilterSecurityInterceptor

        大概是进行Authorization的Filter,待详。

8、AuthenticationManager

顾名思义,AuthenticationManager认证的管理器,提供多个AuthenticationProvider,本例就使用默认的DaoAuthenticationProvider。

        9、AuthenticationEntryPoint

本接口只有一个方法,认证失败后执行。本例就实现该接口,实现认证失败后,返回Json字符串而不是跳转至登录页面。

       10、AccessDeniedHandler

本接口只有一个方法,权限验证失败后执行。本例就实现该接口,实现权限不足时,返回Json字符串而不是返回403错误。

11、AuthenticationFailureHandler

         本接口只有一个方法,登录验证失败后执行。本例就实现该接口,登录验证失败后,返回Json字符串而不是跳整到登录页面。

12、AuthenticationSuccessHandler

    本接口只有一个方法,登录验证成功后执行。本例就实现该接口,登录验证成功后,返回Json字符串而不是整到默认页面或上一页面。

        二、重定义Java对象

         1、重定义登录验证过滤器

<span style="white-space:pre">	</span>public class RestfulUsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
	
	public static final String SPRING_SECURITY_RESTFUL_USERNAME_KEY = "username";
	public static final String SPRING_SECURITY_RESTFUL_PASSWORD_KEY = "password";
	public static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/rest/login";

	private String usernameParameter = SPRING_SECURITY_RESTFUL_USERNAME_KEY;
	private String passwordParameter = SPRING_SECURITY_RESTFUL_PASSWORD_KEY;
	private boolean postOnly = true;

	protected RestfulUsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, "POST"));
	}

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException,
			IOException, ServletException {
		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);

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

		return this.getAuthenticationManager().authenticate(authRequest);
	}
	
	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}


	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}

	protected void setDetails(HttpServletRequest request,
			UsernamePasswordAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	public String getUsernameParameter() {
		return usernameParameter;
	}

	public void setUsernameParameter(String usernameParameter) {
		this.usernameParameter = usernameParameter;
	}

	public String getPasswordParameter() {
		return passwordParameter;
	}

	public void setPasswordParameter(String passwordParameter) {
		this.passwordParameter = passwordParameter;
	}

	public boolean isPostOnly() {
		return postOnly;
	}

	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public void setLoginUrl(String loginUrl) {
		this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(loginUrl, "POST"));
	}

}


2、重实现AuthenticationSuccessHandler

<span style="white-space:pre">	</span>public class RestfulAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

	private RequestCache requestCache = new HttpSessionRequestCache();
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {

		PrintWriter writer;
		String returnStr = "{message:'sucess'}";
		
		response.setStatus(200);
		writer = response.getWriter();
		writer.write(returnStr);
		writer.flush();
		writer.close();
		requestCache.removeRequest(request, response);
		clearAuthenticationAttributes(request);		
	}
    
	protected final void clearAuthenticationAttributes(HttpServletRequest request) {
		HttpSession session = request.getSession(false);

		if (session == null) {
			return;
		}
		session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
	}
	public void setRequestCache(RequestCache requestCache) {
		this.requestCache = requestCache;
	}
}


3、重实现AuthenticationFailureHandler

<span style="white-space:pre">	</span>public class RestfulAuthenticationFailureHandler implements
		AuthenticationFailureHandler {

	@Override
	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {
		PrintWriter writer;
		String returnStr = "{exception:{name:'" + exception.getClass()
				+ "',message:'" + exception.getMessage() + "'}}";
		System.out.println(this.getClass().toString()+":"+returnStr);
		writer = response.getWriter();
		writer.write(returnStr);
		writer.flush();
		writer.close();

	}

}

4、重实现AuthenticationEntryPoint

<span style="white-space:pre">	</span>public class RestfulAuthenticationEntryPoint implements
		AuthenticationEntryPoint {

	@Override
	public void commence(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException authException)
			throws IOException, ServletException {

		PrintWriter writer;
		String returnStr = "{exception:{name:'" + authException.getClass()
				+ "',message:'" + authException.getMessage() + "'}}";
		System.out.println(this.getClass().toString()+":"+returnStr);
		writer = response.getWriter();
		writer.write(returnStr);
		writer.flush();
		writer.close();
	}

}

5、重实现AccessDeniedHandler

<span style="white-space:pre">	</span>public class RestfulAccessDeniedHandlerImpl implements AccessDeniedHandler {

	@Override
	public void handle(HttpServletRequest request,
			HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException,
			ServletException {
		PrintWriter writer;
		String returnStr = "{exception:{name:'" + accessDeniedException.getClass()
				+ "',message:'" + accessDeniedException.getMessage() + "'}}";
		System.out.println(this.getClass().toString()+":"+returnStr);
		writer = response.getWriter();
		writer.write(returnStr);
		writer.flush();
		writer.close();
	}

}

三、组装重定义的Java对象(在Spring Context中)

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
	<http pattern="/**/*.css" security="none" />
	<http pattern="/**/*.js" security="none" />
	<http pattern="/security/**/restful" entry-point-ref="restfulAuthenticationEntryPoint"
		authentication-manager-ref="authenticationManager">
		<access-denied-handler ref="restfulAccessDeniedHandlerImpl" />
	    <custom-filter position="FORM_LOGIN_FILTER"
			ref="restfulUsernamePasswordAuthenticationFilter" />
	    <intercept-url pattern="/security/login/restful" access="permitAll()" />
		<intercept-url pattern="/security/getJson/restful" access="hasRole('ADMIN')" />
		<intercept-url pattern="/**" access="hasRole('USER')" />
		<csrf disabled="true" />
	</http>

	<http pattern="/security/**">
		<form-login login-page="/security/login.jsp"
			login-processing-url="/security/login" default-target-url="/security/index"
			always-use-default-target="false" authentication-failure-url="/security/login.jsp?error=wrong_login_data"
			username-parameter="username" password-parameter="password" />
		<logout logout-url="/security/logout" />
		<intercept-url pattern="/security/index" access="permitAll()" />
		<intercept-url pattern="/security/logout" access="permitAll()" />
		<intercept-url pattern="/security/login.jsp" access="permitAll()" />
		
		<csrf />
	</http>

	<global-method-security pre-post-annotations="enabled" />

	<beans:bean name="bcryptEncoder"
		class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref='myUserDetailsService'>
			<password-encoder ref="bcryptEncoder" />
		</authentication-provider>
	</authentication-manager>
	
	<beans:bean id="myUserDetailsService"
		class="com.winssage.spring.security.userdetails.WinssageUserDetailsService">
		<beans:property name="bcryptPasswordEncoder" ref="bcryptEncoder" />
	</beans:bean>
	

	<!-- restful security start -->
 	<beans:bean id="restfulUsernamePasswordAuthenticationFilter"
		class="com.winssage.spring.security.authentication.RestfulUsernamePasswordAuthenticationFilter">
		<beans:property name="authenticationManager" ref="authenticationManager" />
		<beans:property name="authenticationSuccessHandler" ref="restfulAuthenticationSuccessHandler" />
		<beans:property name="authenticationFailureHandler" ref="restfulAuthenticationFailureHandler" />
		<beans:property name="loginUrl" value="/security/login/restful" />
	</beans:bean>

 	<beans:bean id="restfulAuthenticationSuccessHandler"
		class="com.winssage.spring.security.authentication.RestfulAuthenticationSuccessHandler">
	</beans:bean>
	
 	<beans:bean id="restfulAuthenticationFailureHandler"
		class="com.winssage.spring.security.authentication.RestfulAuthenticationFailureHandler">
	</beans:bean>

	<beans:bean id="restfulAuthenticationEntryPoint"
		class="com.winssage.spring.security.authentication.RestfulAuthenticationEntryPoint" />

	<beans:bean id="restfulAccessDeniedHandlerImpl"
		class="com.winssage.spring.security.access.RestfulAccessDeniedHandlerImpl">
	</beans:bean>
	<!-- restful security end -->

</beans:beans>

四、web.xml中启动Spring Security

<span style="white-space:pre">	</span><filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


五、Maven依赖配置(pom.xml)

<span style="white-space:pre">	</span><properties>
                ...
		<org.springframework-security-version>4.0.1.RELEASE</org.springframework-security-version>	
	</properties>
        <dependencies>
                ...
                <dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${org.springframework-security-version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${org.springframework-security-version}</version>
		</dependency>

	</dependencies>



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值