【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 }

這個類還是跟朋友借鑒的呢,之前我也沒有做過這種,不過這個說實話,真的很有用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM