【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(四)

SpringSecurity(1)


其实啊,这部分我是最不想写的,因为最麻烦的也是这部分,真的是非常非常的麻烦。关于SpringSecurity的配置,让我折腾了好半天,网上的配置方式一大把,但总有一些功能不完全,版本不是最新等等的问题在,所以几乎没有一个教程,是可以整个贯通的。当然我的意思不是说那些不好,那些也不错,但就对于我来说,还不够全面。另外,SpringSecurity的替代品是shiro,据说,两者的区别在于,前者涵盖的范围更广,但前者也相对学习成本更高。又因为SpringSecurity是Spring家族的成员之一,所以在Spring框架下应用的话,可以做到非常高度的自定义,算是非常灵活的安全框架,就是配置起来,真心复杂。

 

SpringSecurity的配置文件


目录:resource/config/spring,文件名:applicationContext-security.xml

  1 <?xml version="1.0" encoding="UTF-8" ?>
  2 <beans xmlns="http://www.springframework.org/schema/beans"
  3        xmlns:sec="http://www.springframework.org/schema/security"
  4        xmlns:aop="http://www.springframework.org/schema/aop"
  5        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  6        xsi:schemaLocation="http://www.springframework.org/schema/beans
  7           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
  8           http://www.springframework.org/schema/aop
  9           http://www.springframework.org/schema/aop/spring-aop.xsd
 10           http://www.springframework.org/schema/security
 11           http://www.springframework.org/schema/security/spring-security.xsd">
 12 
 13     <!--过滤资源 start-->
 14     <!--不进行拦截的静态资源-->
 15     <sec:http pattern="/css/*" security="none"/>
 16     <sec:http pattern="/images/*" security="none"/>
 17     <sec:http pattern="/images/**" security="none"/>
 18     <sec:http pattern="/js/*" security="none"/>
 19     <sec:http pattern="/fonts/*" security="none"/>
 20     <!--不进行拦截的页面-->
 21     <sec:http pattern="/WEB-INF/views/index.jsp" security="none"/>
 22     <!--<sec:http pattern="WEB-INF/views/login.jsp" security="none"/>-->
 23     <!--过滤资源 end-->
 24 
 25     <!--权限配置及自定义登录界面 start-->
 26     <sec:http auto-config="true" access-decision-manager-ref="accessDecisionManager">
 27         <sec:form-login
 28                 login-page="/user/login"
 29                 login-processing-url="/login.do"
 30                 authentication-success-handler-ref="loginController"
 31                 authentication-failure-handler-ref="loginController"/>
 32         <!--登出-->
 33         <sec:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/"/>
 34         <!--session管理及单点登录-->
 35         <sec:session-management session-authentication-strategy-ref="concurrentSessionControlStrategy"/>
 36         <!--资源拦截器配置-->
 37         <sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
 38         <sec:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER"/>
 39     </sec:http>
 40 
 41     <!--自定义验证结果控制器-->
 42     <bean id="loginController" class="com.magic.rent.controller.LoginAuthenticationController">
 43         <property name="successURL" value="/user/home"/>
 44         <property name="failURL" value="/user/login"/>
 45         <property name="attrName" value="loginResult"/>
 46         <property name="byForward" value="false"/>
 47         <property name="userInfo" value="userInfo"/>
 48     </bean>
 49 
 50     <sec:authentication-manager alias="myAuthenticationManager">
 51         <sec:authentication-provider ref="daoAuthenticationProvider"/>
 52     </sec:authentication-manager>
 53 
 54 
 55     <!--权限查询服务-->
 56     <bean id="cachingUserDetailsService"
 57           class="org.springframework.security.config.authentication.CachingUserDetailsService">
 58         <constructor-arg name="delegate" ref="webUserDetailsService"/>
 59         <property name="userCache">
 60             <bean class="org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache">
 61                 <property name="cache" ref="userEhCacheFactory"/>
 62             </bean>
 63         </property>
 64     </bean>
 65 
 66     <bean id="daoAuthenticationProvider"
 67           class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
 68         <property name="messageSource" ref="messageSource"/>
 69         <property name="passwordEncoder" ref="messageDigestPasswordEncoder"/>
 70         <property name="userDetailsService" ref="cachingUserDetailsService"/>
 71         <property name="saltSource" ref="saltSource"/>
 72         <property name="hideUserNotFoundExceptions" value="false"/>
 73     </bean>
 74 
 75     <!--MD5加密盐值-->
 76     <bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource">
 77         <property name="userPropertyToUse" value="username"/>
 78     </bean>
 79 
 80     <!--决策管理器 start-->
 81     <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
 82         <constructor-arg name="decisionVoters">
 83             <list>
 84                 <ref bean="roleVoter"/>
 85                 <ref bean="authenticatedVoter"/>
 86             </list>
 87         </constructor-arg>
 88         <property name="messageSource" ref="messageSource"/>
 89     </bean>
 90     <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
 91         <property name="rolePrefix" value="ROLE_"/>
 92     </bean>
 93     <bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>
 94     <!--决策管理器 end-->
 95 
 96     <!--资源拦截器 start-->
 97     <bean id="filterSecurityInterceptor"
 98           class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
 99         <property name="accessDecisionManager" ref="accessDecisionManager"/>
100         <property name="authenticationManager" ref="myAuthenticationManager"/>
101         <property name="securityMetadataSource" ref="resourceSecurityMetadataSource"/>
102     </bean>
103 
104     <!--方法拦截器 start-->
105     <bean id="methodSecurityInterceptor"
106           class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
107         <property name="accessDecisionManager" ref="accessDecisionManager"/>
108         <property name="authenticationManager" ref="myAuthenticationManager"/>
109         <property name="securityMetadataSource" ref="methodSecurityMetadataSource"/>
110     </bean>
111     <aop:config>
112         <aop:advisor advice-ref="methodSecurityInterceptor" pointcut="execution(* com.magic.rent.service.*.*(..))"
113                      order="1"/>
114     </aop:config>
115     <!--方法拦截器 end-->
116 
117     <!--session管理器 start-->
118     <bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
119         <constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
120         <constructor-arg name="expiredUrl" value="/user/timeout"/>
121     </bean>
122 
123     <bean id="concurrentSessionControlStrategy"
124           class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
125         <constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
126         <property name="maximumSessions" value="1"/>
127     </bean>
128 
129     <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
130     <!--session管理器 end-->
131 </beans>
applicationContext-security.xml

来吧,简单的,从头到尾的解释一下。

首先呢,最先看到的应该是过滤资源的配置:

    <!--过滤资源 start-->
    <!--不进行拦截的静态资源-->
    <sec:http pattern="/css/*" security="none"/>
    <sec:http pattern="/images/*" security="none"/>
    <sec:http pattern="/images/**" security="none"/>
    <sec:http pattern="/js/*" security="none"/>
    <sec:http pattern="/fonts/*" security="none"/>
    <!--不进行拦截的页面-->
    <sec:http pattern="/WEB-INF/views/index.jsp" security="none"/>
    <!--过滤资源 end-->

这些pattern意味着这些资源,不进行安全过滤,即在访问这些资源的时候,不需要进行Security的权限验证,举一个例子:在以“webapp”为根目录的情况下,css文件夹下的任何文件被访问将不进行安全验证,即任何用户都可以毫无顾忌的直接访问这些资源。

  接下来的配置,相当重要,是整个框架的核心部分,如果不理解这部分,将无法好好使用这个框架。

 <!--权限配置及自定义登录界面 start-->
    <sec:http auto-config="true" access-decision-manager-ref="accessDecisionManager">
        <sec:form-login
                login-page="/user/login"
                login-processing-url="/login.do"
                authentication-success-handler-ref="loginController"
                authentication-failure-handler-ref="loginController"/>
        <!--登出-->
        <sec:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/"/>
        <!--session管理及单点登录-->
        <sec:session-management session-authentication-strategy-ref="concurrentSessionControlStrategy"/>
        <!--资源拦截器配置-->
        <sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
        <sec:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER"/>
    </sec:http>

  首先可以看到,“access-decision-manager-ref”是自定义框架的决策管理器(1),这个决策管理器是比如,当一个资源,被配置给3个不同的权限可以访问的时候,你可以决定,是只要拥有三个中的一个权限,就能访问资源,还是至少拥有2个权限,还是必须满足三个权限都拥有的情况下,才能访问资源。这就是决策管理器,就是制定放行规则。所以我们紧接着就要配置它了,这个决策管理器的配置,是这样的:

 

<!--决策管理器 start-->
    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg name="decisionVoters">
            <list>
                <ref bean="roleVoter"/>
                <ref bean="authenticatedVoter"/>
            </list>
        </constructor-arg>
        <property name="messageSource" ref="messageSource"/>
    </bean>
    <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
        <property name="rolePrefix" value="ROLE_"/>
    </bean>
    <bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>
    <!--决策管理器 end-->

 

  值得一提的是,bean:roleVoter中,有一个属性是”rolePrefix“,这个是用于设置角色前缀的。什么是角色前缀呢?先要解释什么是角色。SpringSecurity这个框架,默认的规则是以角色来判断是否有访问权限的,当然这并不符合我们的实际情况,我们使用的时候,更喜欢的是把角色更细化一层,比如,一个角色,具有多个“权限”,然后根据“权限”来判断是否有访问资源的资格。如果有资格,则访问,没资格,则返回403无权访问错误页面(当然默认的403有点丑,大部分情况我们都会对404、500、403这些常见的错误页面来去替换成我们自己编写的页面,这个回头再说。)。而角色权限,就是说,当系统读取到的一个字符串,判断它是否为一个用于表示角色的字符串,就是根据这个前缀来判断的,如果有心得朋友,可以查看“RoleVoter”这个类,可以发现,其实系统对rolePrefix设置了一个默认值,就是“ROLE_”,而我们在这里配置,只是我为了说明这个问题,当然我们可以通过配置Bean来修改这个前缀,不过我个人觉得这个“ROLE_”挺好的,就采用原有的了。那这边设置了前缀,就意味着,我们以后将角色存在数据库当中的时候,就必须给我们的角色定义这个前缀,比如我在数据库中存一个角色为管理员:ROLE_ADMIN。如果我们没有以约定好的前缀来定义角色,系统就会不识别,然后直接报无权限访问。这个也可以在RoleVoter这个类中的“supports”方法中得到查证。顺便说一下,框架会先调用这个supports方法,来校验是否是符合角色前缀的定义规则,如果不符合,根本都不进入后面的对比阶段,直接返回false,然后就被判定为无权访问了。可能就有朋友会想知道从哪里看出,先执行supports这个方法的,我在测试的时候,Debug了整个流程,但是现在已经不记得了,如果有想弄清楚的朋友,可以自行Debug,反正IDEA的Debug有记录整个执行过程,所以只需要在这个supports方法上打一个断点,然后查看上一个步骤就能找到调用的地方。

  接着我们继续往下配置文件的下面看,

 

 <sec:form-login
                login-page="/user/login"
                login-processing-url="/login.do"
                authentication-success-handler-ref="loginController"
                authentication-failure-handler-ref="loginController"/>

 

  这里呢,定义了前台页面中,登录表单的一些规则,

  1. login-page:这个参数,配置的是登录页面的访问地址,因为我们是使用了SpringMVC,所以我自定义了一个Controller用于访问登录页面,而地址就是“/user/login”:
    其实就是很简单的指向了login.jsp这个页面,也没有做什么其他的处理。
  2. login-processing-url:这个参数呢,是当你在jsp或者html页面中,设计登录的表单<form>标签时,其中action元素的地址,就是你配置的这个参数,比如:

  3. authentication-success-handler-ref:这个参数,是定义一个当登录验证成功时要执行操作的控制器。
  4. authentication-failure-handler-ref:这个参数,是定一个,当登录验证失败时,要执行操作的控制器。
    这两个参数,所对应的控制器,我为了简略,就把它们合并成为一个,这个控制器怎么写呢?实际很简单,登录验证成功的控制器呢,就是一个普通的java类,去实现AuthenticationSuccessHandler这个接口的方法“onAuthenticationSuccess”,而登录验证失败呢,就是实现AuthenticationFailureHandler的接口“anAuthenticationFailure”。

  我的实现类:

 

  1 package com.magic.rent.controller;
  2 
  3 import com.magic.rent.pojo.SysUsers;
  4 import com.magic.rent.service.IUserService;
  5 import com.magic.rent.util.HttpUtil;
  6 import com.magic.rent.util.JsonResult;
  7 import org.slf4j.Logger;
  8 import org.slf4j.LoggerFactory;
  9 import org.springframework.beans.factory.InitializingBean;
 10 import org.springframework.beans.factory.annotation.Autowired;
 11 import org.springframework.context.MessageSource;
 12 import org.springframework.context.support.MessageSourceAccessor;
 13 import org.springframework.dao.DataAccessException;
 14 import org.springframework.security.core.Authentication;
 15 import org.springframework.security.core.AuthenticationException;
 16 import org.springframework.security.web.DefaultRedirectStrategy;
 17 import org.springframework.security.web.RedirectStrategy;
 18 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
 19 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
 20 import org.springframework.transaction.annotation.Propagation;
 21 import org.springframework.transaction.annotation.Transactional;
 22 import org.springframework.util.StringUtils;
 23 
 24 import javax.servlet.ServletException;
 25 import javax.servlet.http.HttpServletRequest;
 26 import javax.servlet.http.HttpServletResponse;
 27 import java.io.IOException;
 28 import java.util.Date;
 29 import java.util.Locale;
 30 
 31 public class LoginAuthenticationController implements AuthenticationSuccessHandler, AuthenticationFailureHandler, InitializingBean {
 32 
 33     @Autowired
 34     private IUserService iUserService;
 35 
 36     private String successURL;
 37 
 38     private String failURL;
 39 
 40     private boolean byForward = false;
 41 
 42     private String AttrName;
 43 
 44     private String userInfo;
 45 
 46     private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
 47 
 48     private static Logger logger = LoggerFactory.getLogger(LoginAuthenticationController.class);
 49 
 50     public void setSuccessURL(String successURL) {
 51         this.successURL = successURL;
 52     }
 53 
 54     public void setFailURL(String failURL) {
 55         this.failURL = failURL;
 56     }
 57 
 58     public void setByForward(boolean byForward) {
 59         this.byForward = byForward;
 60     }
 61 
 62     public void setAttrName(String attrName) {
 63         AttrName = attrName;
 64     }
 65 
 66     public void setUserInfo(String userInfo) {
 67         this.userInfo = userInfo;
 68     }
 69 
 70     @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
 71     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
 72         SysUsers users;
 73         JsonResult jsonResult;
 74         try {
 75             users = (SysUsers) authentication.getPrincipal();
 76             Date date = new Date();
 77             users.setLastLogin(date);
 78             users.setLoginIp(HttpUtil.getIP(request));
 79             try {
 80                 iUserService.updateUserLoginInfo(users);
 81             } catch (DataAccessException e) {
 82                 logger.error("登录异常:保存登录数据失败!", e);
 83             }
 84         } catch (Exception e) {
 85             jsonResult = JsonResult.error("用户登录信息保存失败!");
 86             logger.error("登录异常:用户登录信息保存失败!", e);
 87             request.getSession().setAttribute(AttrName, jsonResult);
 88             return;
 89         }
 90         jsonResult = JsonResult.success("登录验证成功!", users);
 91         request.getSession().setAttribute(userInfo, jsonResult);
 92         httpReturn(request, response, true);
 93     }
 94 
 95     @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
 96     public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
 97         JsonResult jsonResult;
 98         logger.info("登录失败:请求IP地址[{}];失败原因:{};", HttpUtil.getIP(request), exception.getMessage());
 99         jsonResult = JsonResult.error(exception.getMessage());
100         request.getSession().setAttribute(AttrName, jsonResult);
101         httpReturn(request, response, false);
102     }
103 
104     public void afterPropertiesSet() throws Exception {
105         if (StringUtils.isEmpty(successURL))
106             throw new ExceptionInInitializerError("成功后跳转的地址未设置!");
107         if (StringUtils.isEmpty(failURL))
108             throw new ExceptionInInitializerError("失败后跳转的地址未设置!");
109         if (StringUtils.isEmpty(AttrName))
110             throw new ExceptionInInitializerError("Attr的Key值未设置!");
111     }
112 
113     private void httpReturn(HttpServletRequest request, HttpServletResponse response, boolean success) throws IOException, ServletException {
114         if (success) {
115             if (this.byForward) {
116                 logger.info("登录成功:Forwarding to [{}]", successURL);
117                 request.getRequestDispatcher(this.successURL).forward(request, response);
118             } else {
119                 logger.info("登录成功:Redirecting to [{}]", successURL);
120                 this.redirectStrategy.sendRedirect(request, response, this.successURL);
121             }
122         } else {
123             if (this.byForward) {
124                 logger.info("登录失败:Forwarding to [{}]", failURL);
125                 request.getRequestDispatcher(this.failURL).forward(request, response);
126             } else {
127                 logger.info("登录失败:Redirecting to [{}]", failURL);
128                 this.redirectStrategy.sendRedirect(request, response, this.failURL);
129             }
130         }
131 
132     }
133 }

 

  估计还是需要简单解释一下,因为这个类我最终也是在Spring中装配的,所以一些字段我也就没有定义,只是做了get和set方法,等待配置。为了防止漏了这些字段的配置,所以我把这个类又另外实现了InitializingBean接口的afterPropertiesSet方法,这个方法可以在Spring框架启动,生产Bean对象对其属性进行装配的时候执行,然后我在这个方法中,对所有需要配置的属性,进行了非空验证。其实这个类的作用很简单,就是登陆成功后,保存登陆信息,然后跳转到登陆后的界面。对了,不能忘了这个LoginAuthenticationController的配置文件了:

1  <!--自定义验证结果控制器-->
2     <bean id="loginController"class="com.magic.rent.controller.LoginAuthenticationController">
3         <property name="successURL" value="/user/home"/>
4         <property name="failURL" value="/user/login"/>
5         <property name="attrName" value="loginResult"/>
6         <property name="byForward" value="false"/>
7         <property name="userInfo" value="userInfo"/>
8     </bean>

  这配置应该算浅显易懂把,因为使用SpringMVC,所以每个地址其实都是SpringMVC的映射地址。

  哦读了!上面那个类,有一个对象,就是JsonResult,这是我用于传输到前端的一个包装工具。

 1 package com.magic.rent.util;
 2 
 3 import org.slf4j.Logger;
 4 import org.slf4j.LoggerFactory;
 5 
 6 import java.io.Serializable;
 7 
 8 /**
 9  * Created by wuxinzhe on 16/9/20.
10  */
11 public class JsonResult implements Serializable {
12 
13     private static final long serialVersionUID = 8134245754393400511L;
14 
15     private boolean status = true;
16     private String message;
17     private Object data;
18     private static Logger logger = LoggerFactory.getLogger(JsonResult.class);
19 
20     public JsonResult() {
21     }
22 
23     public JsonResult(Object data) {
24         this.data = data;
25     }
26 
27     public boolean getStatus() {
28         return status;
29     }
30 
31     public JsonResult setStatus(boolean status) {
32         this.status = status;
33         return this;
34     }
35 
36     public String getMessage() {
37         return message;
38     }
39 
40     public JsonResult setMessage(String message) {
41         this.message = message;
42         return this;
43     }
44 
45     public Object getData() {
46         return data;
47     }
48 
49     public JsonResult setData(Object data) {
50         this.data = data;
51         return this;
52     }
53 
54     public static JsonResult success() {
55         return new JsonResult().setStatus(true);
56     }
57 
58     public static JsonResult success(Object data) {
59         JsonResult jsonResult = success().setData(data);
60         logger.info(jsonResult.toString());
61         return jsonResult;
62     }
63 
64     public static JsonResult success(String message, Object data) {
65         JsonResult jsonResult = success().setData(data).setMessage(message);
66         logger.info(jsonResult.toString());
67         return jsonResult;
68     }
69 
70     public static JsonResult error() {
71         return new JsonResult().setStatus(false);
72     }
73 
74     public static JsonResult error(String message) {
75         JsonResult jsonResult = error().setMessage(message);
76         logger.info(jsonResult.toString());
77         return jsonResult;
78     }
79 
80     @Override
81     public String toString() {
82         return "JsonResult{" +
83                 "status=" + status +
84                 ", message='" + message + '\'' +
85                 ", data=" + data +
86                 '}';
87     }
88 }

这个类还是跟朋友借鉴的呢,之前我也没有做过这种,不过这个说实话,真的很有用。

posted @ 2016-10-21 01:18  梦游的龙猫  阅读(2161)  评论(4编辑  收藏  举报