【Spring实战】----Security4.1.3实现根据请求跳转不同登录页以及登录后根据权限跳转到不同页配置

123 篇文章 0 订阅
27 篇文章 3 订阅

一、背景介绍

上一篇最后总结说了:

1)被认证请求被FilterSecurityInterceptor拦截看有没有对应权限,如果没有抛异常给ExceptionTranslationFilter
2)ExceptionTranslationFilter缓存原请求,利用LoginUrlAuthenticationEntryPoint入口跳转到登录界面
3)用户在登录界面填写登录信息后,提交,经过UsernamePasswordAuthenticationFilter对填写的信息和从数据源中获取的信息进行对比,成功则授权权限,并通过登录成功后入口SavedRequestAwareAuthenticationSuccessHandler跳转回原请求页面(跳转时有从缓存中对请求信息的恢复)
4)登录完成后返回原请求,由FilterSecurityInterceptor进行权限的验证(大部分工作有AbstractSecurityInterceptor来做),根据登录成功后生成的Authentication(Authentication authentication = SecurityContextHolder.getContext().getAuthentication();由SecurityContextHolder持有,而其中的SecurityContext由 SecurityContextPersistentFilter保存到session中从而实现request共享 )中的权限和请求所需的权限对比,如果一致则成功执行,如果权限不正确则返回403错误码
5)以上均是默认情况下,没有经过配置的执行过程,当然可以自定义LoginUrlAuthenticationEntryPoint和SavedRequestAwareAuthenticationSuccessHandler实现根据不同的请求所需权限跳转到不同登录页面及授权成功后根据权限跳转到不同页面,以及返回403错误码时跳转到对应的页面(AccessDeniedHandlerImpl)在下一篇中会对其进行实现


那么这里要实现题目中说的(登录后根据权限跳转到不同页是指直接点击登录的情况,如果是有原请求跳转过来的,登录成功并且认证成功后跳转到原请求页),需要如下操作

1)自定义LoginUrlAuthenticationEntryPoint实现跳转到不同登录页,如用户订单请求跳转到用户登录页,管理中心请求跳转到管理员登录页

2)自定义SavedRequestAwareAuthenticationSuccessHandler实现直接点击登录成功后跳转到指定的页,如用户登录后跳转到首页,管理员登陆后跳转到管理中心

3)此问题涉及了两种权限的用户登录,ROLE_USER及ROLE_MANAGER,还需要配置AccessDeniedHandlerImpl来处理虽然登录成功了确没有权限访问的情况。

4)还需要自定义SimpleUrlAuthenticationFailureHandler来实现登录失败的情况,主要是用户不存在或密码错误问题。这种情况下能够实现从哪个登录页面过来的还是返回原登录页,并携带错误信息

5)还需要配置login-processing-url属性,能够拦截/manager/login和/login提交,从而经过UsernamePasswordAuthenticationFilter时对其进行登录验证(requiresAuthentication(request, response)判断)

下面对其一一说明

二、全部配置如下:

<?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.xsd
		http://www.springframework.org/schema/security
		http://www.springframework.org/schema/security/spring-security.xsd">
		
	<http auto-config="true" use-expressions="true" entry-point-ref="myAuthenticationEntryPoint" >
		<form-login 
			login-page="/login"
            login-processing-url="/**/login"
            authentication-failure-handler-ref="myAuthenticationFailureHandler"
            authentication-success-handler-ref="myAuthenticationSuccessHandler" />   
         <!-- 认证成功用自定义类myAuthenticationSuccessHandler处理 -->
         
         <logout logout-url="/logout" 
				logout-success-url="/" 
				invalidate-session="true"
				delete-cookies="JSESSIONID"/>
		
		<!-- 登录成功后拒绝访问跳转的页面 -->		
		<access-denied-handler error-page="/security/deny" />
		
		<csrf disabled="true" />
		<intercept-url pattern="/order/*" access="hasRole('ROLE_USER')"/>
		<intercept-url pattern="/manager" access="hasRole('ROLE_MANAGER')"/>
	</http>
	
	<!-- 使用自定义类myUserDetailsService从数据库获取用户信息 -->
	<authentication-manager>  
        <authentication-provider user-service-ref="myUserDetailsService">  
        	<!-- 加密 -->
            <password-encoder hash="md5">
            </password-encoder>
        </authentication-provider>
    </authentication-manager>
    
    <!-- 被认证请求根据所需权限跳转到不同的登录界面 -->
    <beans:bean id="myAuthenticationEntryPoint" 
    	class="com.mango.jtt.springSecurity.MyAuthenticationEntryPoint">
    	<beans:property name="authEntryPointMap" ref="loginFormsMap"></beans:property>
    	<beans:constructor-arg name="loginFormUrl" value="/login"></beans:constructor-arg>
    </beans:bean>
    
    <!-- 根据不同请求所需权限跳转到不同的登录界面 -->
	<beans:bean id="loginFormsMap" class="java.util.HashMap">
		<beans:constructor-arg>
			<beans:map>
				<beans:entry key="/user/**" value="/login" />
				<beans:entry key="/manager/**" value="/manager/login" />
				<beans:entry key="/**" value="/login" />
			</beans:map>
		</beans:constructor-arg>
	</beans:bean>
	
	<!-- 登录且授权成功后控制 -->
    <beans:bean id="myAuthenticationSuccessHandler" 
    	class="com.mango.jtt.springSecurity.MyAuthenticationSuccessHandler">
    	<beans:property name="authDispatcherMap" ref="dispatcherMap"></beans:property>
    </beans:bean>
	
	<!-- 根据不同的权限,跳转到不同的页面(直接点击登录页面用) -->
	<beans:bean id="dispatcherMap" class="java.util.HashMap">
	  	<beans:constructor-arg>
		    <beans:map>
				<beans:entry key="ROLE_USER" value="/"/>
				<beans:entry key="ROLE_MANAGER" value="/manager"/>
	      	</beans:map>
	  	</beans:constructor-arg>
	</beans:bean>
	
	<!-- 登录失败后控制 -->
    <beans:bean id="myAuthenticationFailureHandler" 
    	class="com.mango.jtt.springSecurity.MyAuthenticationFailureHandler">
    	<beans:property name="loginEntry" ref="myAuthenticationEntryPoint"></beans:property>
    </beans:bean>
</beans:beans>

三、MyAuthenticationEntryPoint配置

 <!-- 被认证请求根据所需权限跳转到不同的登录界面 -->
    <beans:bean id="myAuthenticationEntryPoint" 
    	class="com.mango.jtt.springSecurity.MyAuthenticationEntryPoint">
    	<beans:property name="authEntryPointMap" ref="loginFormsMap"></beans:property>
    	<beans:constructor-arg name="loginFormUrl" value="/login"></beans:constructor-arg>
    </beans:bean>
    
    <!-- 根据不同请求所需权限跳转到不同的登录界面 -->
	<beans:bean id="loginFormsMap" class="java.util.HashMap">
		<beans:constructor-arg>
			<beans:map>
				<beans:entry key="/user/**" value="/login" />
				<beans:entry key="/manager/**" value="/manager/login" />
				<beans:entry key="/**" value="/login" />
			</beans:map>
		</beans:constructor-arg>
	</beans:bean>


根据请求路径跳转到不同的登录页面(其他实现方式也可),其中key是匹配的路径,value是要跳转的登录页,关键代码如下:

/**
 * 被认证请求向登录页面跳转的控制 根据被请求所需权限向不同登录页面跳转
 * 
 * @author HHL
 * 
 * @date 2016年12月20日
 */
public class MyAuthenticationEntryPoint extends
		LoginUrlAuthenticationEntryPoint {

	public MyAuthenticationEntryPoint(String loginFormUrl) {
		super(loginFormUrl);
	}

	private Map<String, String> authEntryPointMap;
	private PathMatcher pathMatcher = new AntPathMatcher();

	@Override
	protected String determineUrlToUseForThisRequest(
			HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) {
		String requestURI = request.getRequestURI().replace(
				request.getContextPath(), "");
		for (String url : this.authEntryPointMap.keySet()) {
			if (this.pathMatcher.match(url, requestURI)) {
				return this.authEntryPointMap.get(url);
			}
		}
		return super.determineUrlToUseForThisRequest(request, response,
				exception);
	}


	public PathMatcher getPathMatcher() {
		return pathMatcher;
	}

	public void setPathMatcher(PathMatcher pathMatcher) {
		this.pathMatcher = pathMatcher;
	}


	public Map<String, String> getAuthEntryPointMap() {
		return authEntryPointMap;
	}

	public void setAuthEntryPointMap(Map<String, String> authEntryPointMap) {
		this.authEntryPointMap = authEntryPointMap;
	}

}

该类重写了父类的determineUrlToUseForThisRequest,实现了根据请求路径返回不同的登录页路径,从而在父类的commence方法中跳转到根据请求路径返回的登录页。其中spring的AntPathMatcher类及AntPathRequestMatcher可以实现路径的匹配工作。

这里实现了"/user/**"相关的路径跳转到用户登录页面"/login","/manager/**"相关的页面跳转到管理员登录页面"/manager/login",上面两者没有匹配的"/**",均跳转到用户登录页面"/login"。当然上述的前提条件是请求路径需要权限,也就是有如下配置:

<intercept-url pattern="/order/**" access="hasRole('ROLE_USER')"/>
		<intercept-url pattern="/manager" access="hasRole('ROLE_MANAGER')"/>
否则,不需要权限的请求,也不需要跳转到登录页


四、myAuthenticationSuccessHandler配置

主要配置

<!-- 授权成功后控制 -->
    <beans:bean id="myAuthenticationSuccessHandler" 
    	class="com.mango.jtt.springSecurity.MyAuthenticationSuccessHandler">
    	<beans:property name="authDispatcherMap" ref="dispatcherMap"></beans:property>
    </beans:bean>
	
	<!-- 根据不同的权限,跳转到不同的页面(直接点击登录页面用) -->
	<beans:bean id="dispatcherMap" class="java.util.HashMap">
	  	<beans:constructor-arg>
		    <beans:map>
				<beans:entry key="ROLE_USER" value="/"/>
				<beans:entry key="ROLE_MANAGER" value="/manager"/>
	      	</beans:map>
	  	</beans:constructor-arg>
	</beans:bean>

其中map中的key为登录后的用户权限,value代表要直接点击登录的情况下登录成功后要跳转的页面,关键代码处理

/**
 * 登录授权成功后操作控制,如果是直接点击登录的情况下,根据授权权限跳转不同页面; 否则跳转到原请求页面
 * 
 * @author HHL
 * @date
 * 
 */
public class MyAuthenticationSuccessHandler extends
		SavedRequestAwareAuthenticationSuccessHandler {
	private Map<String, String> authDispatcherMap;
	private RequestCache requestCache = new HttpSessionRequestCache();

	@Autowired
	private IUserService userService;

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {
		// 获取用户权限
		Collection<? extends GrantedAuthority> authCollection = authentication
				.getAuthorities();
		if (authCollection.isEmpty()) {
			return;
		}

		// 认证成功后,获取用户信息并添加到session中
		UserDetails userDetails = (UserDetails) authentication.getPrincipal();
		MangoUser user = userService.getUserByName(userDetails.getUsername());
		request.getSession().setAttribute("user", user);
		
		String url = null;
		// 从别的请求页面跳转过来的情况,savedRequest不为空
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		if (savedRequest != null) {
			url = savedRequest.getRedirectUrl();
		}

		// 直接点击登录页面,根据登录用户的权限跳转到不同的页面
		if (url == null) {
			for (GrantedAuthority auth : authCollection) {
				url = authDispatcherMap.get(auth.getAuthority());
			}
			getRedirectStrategy().sendRedirect(request, response, url);
		}

		super.onAuthenticationSuccess(request, response, authentication);
	
	}


	public RequestCache getRequestCache() {
		return requestCache;
	}

	public void setRequestCache(RequestCache requestCache) {
		this.requestCache = requestCache;
	}


	public Map<String, String> getAuthDispatcherMap() {
		return authDispatcherMap;
	}

	public void setAuthDispatcherMap(Map<String, String> authDispatcherMap) {
		this.authDispatcherMap = authDispatcherMap;
	}

}

其中用从requestCache中获取到的savedRequest来判断是否是直接点击登录还是从其他页面跳转过来的,上一篇也说过从savedRequest获取实质上是从session中获取,因此这里的requestCache实例可以为任一实例。

这里就实现了如果是直接点击登录的成功后,用户登录的跳转到首页"/",管理员登录的就跳转到管理中心“/manager”;如果是从其他请求页过来的还是返回原请求页面。

五、myAuthenticationFailureHandler

登录失败后,没有用户或者密码错误的情况下,主要配置

<!-- 登录失败后控制 -->
    <beans:bean id="myAuthenticationFailureHandler" 
    	class="com.mango.jtt.springSecurity.MyAuthenticationFailureHandler">
    	<beans:property name="loginEntry" ref="myAuthenticationEntryPoint"></beans:property>
    </beans:bean>


其中依赖了myAuthenticationEntryPoint,就是要根据登录路径,返回到原登录页面,并携带错误信息,这里就需要管理登录的form action设置不同

<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=UTF-8"%>
<%@ include file="../../includes/taglibs.jsp"%>
<!DOCTYPE html>
<html>
<head>
    <title>Mango-managerLogin</title>
	<meta name="menu" content="home" />    
</head>

<body>

<h1>请管理员登录!</h1>

<div style="text-align:center">
	<form action="<c:url value='/manager/login' />" method="post">
		<c:if test="${not empty error}">
			<p style="color:red">${error}</p>
		</c:if>
      <table>
         <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"/></td>
         </tr>
         <tr>
            <td>密码:</td>
            <td><input type="password" name="password"/></td>
         </tr>
         <tr>
            <td colspan="2" align="center">
                <input type="submit" value="登录"/>
                <input type="reset" value="重置"/>
            </td>
         </tr>
      </table>
   </form>
</div>

</body>
</html>


其中action是/manager/login,和用户登录页中的/login是区分开的,这样利用myAuthenticationEntryPoint就可以根据路径返回到对应的登录界面,关键代码:

/**
 * 登录失败控制
 * 
 * @author HHL
 * 
 * @date 2016年12月20日
 */
public class MyAuthenticationFailureHandler extends
		SimpleUrlAuthenticationFailureHandler {
	private MyAuthenticationEntryPoint loginEntry;

	public MyAuthenticationEntryPoint getLoginEntry() {
		return loginEntry;
	}

	public void setLoginEntry(MyAuthenticationEntryPoint loginEntry) {
		this.loginEntry = loginEntry;
	}

	@Override
	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {
		// 从loginEntry中获取登录失败要跳转的url,并加上错误信息error
		String authenfailureUrl = this.loginEntry
				.determineUrlToUseForThisRequest(request, response, exception);
		authenfailureUrl = authenfailureUrl + "?error";
		super.setDefaultFailureUrl(authenfailureUrl);
		super.onAuthenticationFailure(request, response, exception);

	}

}

该类覆盖了父类的onAuthenticationFailure,根据请求路径,如果是“/manager/login”就和MyAuthenticationEntryPoint中的“/manager/**”相匹配,返回路径为“/manager/login”‘;“/login”的情况也类似,返回到“/login”;然后添加上?error,并设置到父类的setDefaultFailureUrl,由父类的onAuthenticationFailure执行跳转。

当然contoller中要如下配置:

/**
 * @author HHL
 * 
 * 
 *         管理员控制类
 */
@Controller
public class ManagerController {

	/**
	 * 管理中心首页
	 * 
	 * @param model
	 * @return
	 */
	@RequestMapping("/manager")
	public String login(Model model) {
		return "manager/index";
	}
	
	/**
	 * 显示登录页面用,主要是显示错误信息
	 * 
	 * @param model
	 * @param error
	 * @return
	 */
	@RequestMapping("/manager/login")
	public String login(Model model,
			@RequestParam(value = "error", required = false) String error) {
		if (error != null) {
			model.addAttribute("error", "用户名或密码错误");
		}
		return "manager/login";
	}
}

如果登录失败则error为"",满足不为null的条件


六、login-processing-url配置

既然两个登录页中的form action不同,那么login-processing-url配置对两者均要进行拦截:

login-processing-url="/**/login"

这里的"/**/login"对"/manager/login"和"/login"均能实现拦截,由UsernamePasswordAuthenticationFilter进行拦截,match部分是由Spring中的AntPathRequestMatcher实现的。

工作在父类AbstractAuthenticationProcessingFilter的requiresAuthentication方法

/**
	 * Indicates whether this filter should attempt to process a login request for the
	 * current invocation.
	 * <p>
	 * It strips any parameters from the "path" section of the request URL (such as the
	 * jsessionid parameter in <em>http://host/myapp/index.html;jsessionid=blah</em>)
	 * before matching against the <code>filterProcessesUrl</code> property.
	 * <p>
	 * Subclasses may override for special requirements, such as Tapestry integration.
	 *
	 * @return <code>true</code> if the filter should attempt authentication,
	 * <code>false</code> otherwise.
	 */
	protected boolean requiresAuthentication(HttpServletRequest request,
			HttpServletResponse response) {
		return requiresAuthenticationRequestMatcher.matches(request);
	}

可见UsernamePasswordAuthenticationFilter完成了用户的登录工作(用户是否存在,密码是否正确,和数据源只能的用户信息对比)


七、access-denied-handler

访问拒绝,该配置中有两种用户权限'ROLE_USER'和'ROLE_MANAGER',当用户登录时具有'ROLE_USER'权限,当管理员登录时具有'ROLE_MANAGER'权限;因此当用户登录的情况下访问管理中心,这个时候权限就不充分,Security系统会抛403错误,拒绝访问,那么这里就需要配置错误页,根据access-denied-handler标签就可以配置

<!-- 登录成功后拒绝访问跳转的页面 -->		
		<access-denied-handler error-page="/security/deny" />


路径为"/security/deny"

/**
 * security控制类
 * 
 * @author HHL
 * 
 * @date 2016年12月20日
 */
@Controller
public class SecurityController {

	/**
	 * 拒绝访问时跳转页面
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping("/security/deny")
	public String deny(HttpServletRequest request,HttpServletResponse response){
		return "security_deny";
	}
}

页面为security_deny.jsp

<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=UTF-8"%>
<%@ include file="../includes/taglibs.jsp"%>
<!DOCTYPE html>
<html>
<head>
    <title>Mango-deny</title>
	<meta name="menu" content="home" />    
</head>
<body>
<h1>对不起,您没有权限访问该页面!</h1>
       
<div style="text-align:center">
	<img src="<c:url value='/resources/images/nonono.jpg'/>"/>
	<p>
		<a href="<c:url value='/'/>">首页</a>
		<a href="<c:url value='/manager'/>">管理中心</a>
		<a href="<c:url value='/logout'/>" >退出登录</a>
	</p>
</div>
</body>
</html>


效果如下:




完整代码如下:https://github.com/honghailiang/SpringMango





  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值