Java後臺框架篇--Springsecurity(二)

剩下的頁面代碼

本來想給這個demo的源碼出來的,但是筆者覺得,通過這個教程一步一步讀下來,並自己敲一遍代碼,會比直接運行一遍demo印象更深刻,並且更容易理解裏面的原理。

而且我的源碼其實都公佈出來了:

login.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<% @page language= "java" import = "java.util.*" pageEncoding= "UTF-8" %> 
<!DOCTYPEhtmlPUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
<html> 
<head> 
<title>登錄</title> 
</head> 
<body> 
     <form action = "j_spring_security_check" method= "POST"
     <table> 
         <tr> 
             <td>用戶:</td> 
             <td><input type = 'text' name= 'j_username' ></td> 
         </tr> 
         <tr> 
             <td>密碼:</td> 
             <td><input type = 'password' name= 'j_password' ></td> 
         </tr> 
         <tr> 
             <td><input name = "reset" type= "reset" ></td> 
             <td><input name = "submit" type= "submit" ></td> 
         </tr> 
     </table> 
     </form> 
</body> 
</html>

index.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<% @page language= "java" import = "java.util.*" pageEncoding= "UTF-8" %>  
<% @taglib prefix= "sec" uri= "http://www.springframework.org/security/tags" %>  
<!DOCTYPEHTMLPUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 
<html> 
 
<head> 
 
<title>My JSP 'index.jsp' starting page</title>  
</head> 
 
<body> 
       <h3>這是首頁</h3>歡迎 
     <sec:authentication property = "name" /> ! 
 
        
     <a href= "admin.jsp" >進入admin頁面</a>  
     <a href= "other.jsp" >進入其它頁面</a>  
</body> 
 
</html>

admin.jsp:

1
2
3
4
5
6
7
8
9
10
11
<% @page language= "java" import = "java.util.*" pageEncoding= "utf-8" %> 
<!DOCTYPEHTMLPUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
<html> 
<head> 
<title>My JSP 'admin.jsp' starting page</title> 
</head> 
<body> 
     歡迎來到管理員頁面. 
       
</body> 
</html>

accessDenied.jsp:

1
2
3
4
5
6
7
8
9
10
11
<%@page language="java" import="java.util.*" pageEncoding="utf-8"%> 
<!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN"> 
< html
< head
< title >My JSP 'admin.jsp' starting page</ title
</ head
< body
     歡迎來到管理員頁面. 
       
</ body
</ html >

other.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 
<% 
String path = request.getContextPath(); 
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 
%> 
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
< html
   < head
     < base href="<%=basePath%>"> 
 
     < title >My JSP 'other.jsp' starting page</ title
 
     < meta http-equiv = "pragma" content = "no-cache"
     < meta http-equiv = "cache-control" content = "no-cache"
     < meta http-equiv = "expires" content = "0" >     
     < meta http-equiv = "keywords" content = "keyword1,keyword2,keyword3"
     < meta http-equiv = "description" content = "This is my page"
     <!--
     <link rel="stylesheet" type="text/css" href="styles.css">
     --> 
 
   </ head
 
   < body
     < h3 >這裏是Other頁面</ h3
   </ body
</ html >

項目圖:

最後的話:

雖然筆者沒給讀者們demo,但是所有源碼和jar包都在這個教程裏面,爲什麼不直接給?筆者的目的是讓讀者跟着教程敲一遍代碼,使印象深刻(相信做這行的都知道,同樣一段代碼,看過和敲過的區別是多麼的大),所以不惜如此來強迫大家了。

由於筆者有經常上csdn博客的習慣,所以讀者有什麼不懂的(或者指教的),筆者盡力解答。

轉載請標註本文鏈接:http://blog.csdn.net/u012367513/article/details/38866465

補充:

(2014年11月21日第一次補充):

第一點:

MyUserDetailService這個類負責的是隻是獲取登陸用戶的詳細信息(包括密碼、角色等),不負責和前端傳過來的密碼對比,只需返回User對象,後會有其他類根據User對象對比密碼的正確性(框架幫我們做)。

第二點:

記得MyInvocationSecurityMetadataSource這個類是負責的是獲取角色與url資源的所有對應關係,並根據url查詢對應的所有角色。

今天爲一個項目搭安全架構時,第一,發現上面MyInvocationSecurityMetadataSource這個類的代碼有個bug:

上面的代碼中,將所有的對應關係緩存到resourceMap,key是url,value是這個url對應所有角色。

getAttributes方法中,只要匹配到一個url就返回這個url對應所有角色,不再匹配後面的url,問題來了,當url有交集時,就有可能漏掉一些角色了:如有兩個 url ,第一個是 /** ,第二個是 /role1/index.jsp ,第一個當然需要很高的權限了(因爲能匹配所有 url ,即可以訪問所有 url ),假設它需要的角色是 ROLE_ADMIN (不是一般人擁有的),第二個所需的角色是 ROLE_1 。    當我用 ROLE_1 這個角色訪問 /role1/index.jsp 時,在getAttributes方法中,當先迭代了 /** 這個url,它就能匹配 /role1/index.jsp 這個url,並直接返回 /** 這個url對應的所有角色(在這,也就ROLE_ADMIN)給MyAccessDecisionManager這個投票類,  MyAccessDecisionManager這個類中再對比 用戶的角色 ROLE_1 ,就會發現不匹配。    最後,明明可以有權訪問的 url ,卻不能訪問了。

第二,之前不是說緩存所有對應關係,需要讀者自己寫sessionFactory(因爲在實例化這個類時,配置的sessionFactory可能還沒實例化或dao還沒加載好),既然這樣,那筆者可以不在構造方法中加載對應關係,可以在第一次調用getAttributes方法時再加載(用靜態變量緩存起來,第二次就不用再加載了,     注:其實這樣不是很嚴謹,不過筆者這裏的對應關係是不變的,單例性不需很強,更嚴謹的請參考筆者另一篇博文設計模式之單件模式)。

修改過的MyInvocationSecurityMetadataSource類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.lcy.bookcrossing.springSecurity; 
 
import java.util.ArrayList; 
import java.util.Collection; 
import java.util.HashMap; 
import java.util.HashSet; 
import java.util.Iterator; 
import java.util.List; 
import java.util.Map; 
import java.util.Set; 
 
import javax.annotation.Resource; 
 
import org.springframework.security.access.ConfigAttribute; 
import org.springframework.security.access.SecurityConfig; 
import org.springframework.security.web.FilterInvocation; 
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 
 
import com.lcy.bookcrossing.bean.RoleUrlResource; 
import com.lcy.bookcrossing.dao.IRoleUrlResourceDao; 
import com.lcy.bookcrossing.springSecurity.tool.AntUrlPathMatcher; 
import com.lcy.bookcrossing.springSecurity.tool.UrlMatcher; 
 
public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {  
     private UrlMatcher urlMatcher = new AntUrlPathMatcher();  
//  private static Map<String, Collection<ConfigAttribute>> resourceMap = null; 
 
     //將所有的角色和url的對應關係緩存起來 
     private static List<RoleUrlResource> rus = null
 
     @Resource 
     private IRoleUrlResourceDao roleUrlDao; 
 
     //tomcat啓動時實例化一次 
     public MyInvocationSecurityMetadataSource() { 
//      loadResourceDefine();   
         }    
     //tomcat開啓時加載一次,加載所有url和權限(或角色)的對應關係 
     /*private void loadResourceDefine() {
         resourceMap = new HashMap<String, Collection<ConfigAttribute>>(); 
         Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>(); 
         ConfigAttribute ca = new SecurityConfig("ROLE_USER");
         atts.add(ca); 
         resourceMap.put("/index.jsp", atts);  
         Collection<ConfigAttribute> attsno =new ArrayList<ConfigAttribute>();
         ConfigAttribute cano = new SecurityConfig("ROLE_NO");
         attsno.add(cano);
         resourceMap.put("/other.jsp", attsno);   
         }  */ 
 
     //參數是要訪問的url,返回這個url對於的所有權限(或角色) 
     public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {  
         // 將參數轉爲url     
         String url = ((FilterInvocation)object).getRequestUrl();    
 
         //查詢所有的url和角色的對應關係 
         if(rus == null){ 
         rus = roleUrlDao.findAll(); 
        
 
         //匹配所有的url,並對角色去重 
         Set<String> roles = new HashSet<String>(); 
         for(RoleUrlResource ru : rus){ 
             if (urlMatcher.pathMatchesUrl(ru.getUrlResource().getUrl(), url)) {  
                         roles.add(ru.getRole().getRoleName()); 
                 }      
        
         Collection<ConfigAttribute> cas = new ArrayList<ConfigAttribute>();  
         for(String role : roles){ 
             ConfigAttribute ca = new SecurityConfig(role); 
             cas.add(ca);  
        
         return cas; 
 
         /*Iterator<String> ite = resourceMap.keySet().iterator(); 
         while (ite.hasNext()) {         
             String resURL = ite.next();  
             if (urlMatcher.pathMatchesUrl(resURL, url)) { 
                 return resourceMap.get(resURL);         
                 }       
            
         return null;    */ 
         }   
     public boolean supports(Class<?>clazz) {  
             return true ;   
             }  
     public Collection<ConfigAttribute> getAllConfigAttributes() {  
         return null ;   
        
     }

以上代碼,在getAttributes方法中緩存起所有的對應關係(可以使用依賴注入了),並匹配所有 url ,對角色進行去重(因爲多個url可能有重複的角色),這樣就能修復那個bug了。

轉載請標註本文鏈接:http://blog.csdn.net/u012367513/article/details/38866465


(2014年12月10日第二次補充):

這次補充不是修上面的bug,而是添加新功能。

我們知道,上面的實現的登陸界面只能傳遞兩個參數(j_username,j_password),而且是固定的。

總是有一個項目需求,我們的角色(ROLE_)不是很多,只需在登陸界面選擇一種角色就行了,那麼如何將角色類型傳遞到spring security呢,現在筆者對配置文件再修改修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<? xml version = "1.0" encoding = "UTF-8" ?> 
< b:beans xmlns = "http://www.springframework.org/schema/security" 
     xmlns:b = "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.1.xsd"> 
 
  <!-- 配置不需要安全管理的界面 --> 
      < http pattern = "/jsp/css/**" security = "none" ></ http
      < http pattern = "/jsp/js/**" security = "none" ></ http
      < http pattern = "/jsp/images/**" security = "none" ></ http
      < http pattern = "/login.jsp" security = "none" /> 
      < http pattern = "/accessDenied.jsp" security = "none" /> 
          < http pattern = "/index.jsp" security = "none" /> 
 
         < http use-expressions = 'true' entry-point-ref = "myAuthenticationEntryPoint" access-denied-page = "/accessDenied.jsp"
 
                 <!-- 使用自己自定義的登陸認證過濾器 --> <!-- 這裏一定要註釋掉,因爲我們需要重寫它的過濾器 --> 
                 <!-- <form-login login-page="/login.jsp" 
                 authentication-failure-url="/accessDenied.jsp"     
         default-target-url="/index.jsp" 
                  /> --> 
                 <!--訪問/admin.jsp資源的用戶必須具有ROLE_ADMIN的權限 --> 
                 <!-- <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> --> 
                 <!--訪問/**資源的用戶必須具有ROLE_USER的權限 --> 
                 <!-- <intercept-url pattern="/**" access="ROLE_USER" /> --> 
                 < session-management
                         < concurrency-control max-sessions = "1" 
                                 error-if-maximum-exceeded = "false" /> 
                 </ session-management
 
                 <!-- 認證和授權 --> <!-- 重寫登陸認證的過濾器,使我們可以拿到任何參數  --> 
                 < custom-filter ref = "myAuthenticationFilter" position = "FORM_LOGIN_FILTER"  /> 
                 < custom-filter ref = "myFilter" before = "FILTER_SECURITY_INTERCEPTOR" /> 
 
                  <!-- 登出管理 --> 
         < logout invalidate-session = "true" logout-url = "/j_spring_security_logout" /> 
 
         </ http
 
         <!-- 未登錄的切入點 --> <!-- 需要有個切入點 --> 
     < b:bean id = "myAuthenticationEntryPoint" class = "org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"
         < b:property name = "loginFormUrl" value = "/login.jsp" ></ b:property
     </ b:bean
 
         <!-- 登錄驗證器:用戶有沒有登錄的資格 --> <!-- 這個就是重寫的認證過濾器 --> 
     < b:bean id = "myAuthenticationFilter" class = "com.lcy.springSecurity.MyAuthenticationFilter"
         < b:property name = "authenticationManager" ref = "authenticationManager" /> 
         < b:property name = "filterProcessesUrl" value = "/j_spring_security_check" /> 
         < b:property name = "authenticationSuccessHandler"
             < b:bean class = "org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"
                 < b:property name = "defaultTargetUrl" value = "/index.jsp" /> 
             </ b:bean
         </ b:property
         < b:property name = "authenticationFailureHandler"
             < b:bean class = "org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"
                 < b:property name = "defaultFailureUrl" value = "/accessDenied.jsp" /> 
             </ b:bean
         </ b:property
     </ b:bean
 
         <!--一個自定義的filter,必須包含 authenticationManager,accessDecisionManager,securityMetadataSource三個屬性,我們的所有控制將在這三個類中實現,解釋詳見具體配置 --> 
         < b:bean id = "myFilter" 
                 class = "com.lcy.springSecurity.MyFilterSecurityInterceptor"
                 < b:property name = "authenticationManager" ref = "authenticationManager" /> 
                 < b:property name = "accessDecisionManager" ref = "myAccessDecisionManagerBean" /> 
                 < b:property name = "securityMetadataSource" ref = "securityMetadataSource" /> 
         </ b:bean
         <!--驗證配置,認證管理器,實現用戶認證的入口,主要實現UserDetailsService接口即可 --> 
         < authentication-manager alias = "authenticationManager"
                 < authentication-provider user-service-ref = "myUserDetailService"
                         <!--如果用戶的密碼採用加密的話 <password-encoder hash="md5" /> --> 
                         <!-- <password-encoder hash="md5" /> --> 
                 </ authentication-provider
         </ authentication-manager
         <!--在這個類中,你就可以從數據庫中讀入用戶的密碼,角色信息,是否鎖定,賬號是否過期等 --> 
         < b:bean id = "myUserDetailService" class = "com.lcy.springSecurity.MyUserDetailService" /> 
         <!--訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源 --> 
         < b:bean id = "myAccessDecisionManagerBean" 
                 class = "com.lcy.springSecurity.MyAccessDecisionManager"
         </ b:bean
         <!--資源源數據定義,將所有的資源和權限對應關係建立起來,即定義某一資源可以被哪些角色訪問 --> 
         < b:bean id = "securityMetadataSource" 
                 class = "com.lcy.springSecurity.MyInvocationSecurityMetadataSource" />  
 
  </ b:beans >

我現在的項目需要的是,角色只要管理員、教師、學生,所以MyAuthenticationFilter(重寫的認證過濾器):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.lcy.springSecurity; 
 
import javax.annotation.Resource; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
 
import org.springframework.security.authentication.AuthenticationServiceException; 
import
相關文章
相關標籤/搜索