學習項目中用到了Spring Security進行權限管理,比較好用,使用到的方式是基於xml配置文件的,相比較java代碼配置簡潔很多,故作筆記整理共享
首先貼上全部spring security配置文件代碼,還是一樣,因爲我這個是maven項目,所以需要提前在pom文件裏寫好依賴,以下pom文件內容僅供參考
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
<spring.security.version>5.0.1.RELEASE</spring.security.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java編譯插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>8080</port>
<!-- 請求路徑 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
依賴寫好了,包都導好了後,接下來就是要打開web.xml文件進行相關配置了
首先我們應當在<context-param>下配置加載類路徑的配置文件,和記載Spring配置文件是一樣的
寫好這個以後我們就應當寫一個過濾器,名字爲springSecurityFilterChain,名字切記不可寫錯,因爲Spring-Security底層其實就是將springSecurityFilterChain交給springIOC容器來進行管理。
/*代表所有的都要經過該安全框架認證。
接下來就是該安全框架的具體xml配置文件了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
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">
<!-- 配置不攔截的資源 -->
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>
<!--
配置具體的規則
auto-config="true" 不用自己編寫登錄的頁面,框架提供默認登錄頁面
use-expressions="false" 是否使用SPEL表達式(沒學習過)
-->
<security:http auto-config="true" use-expressions="false">
<!-- 配置具體的攔截的規則 pattern="請求路徑的規則" access="訪問系統的人,必須有ROLE_USER的角色" -->
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<!-- 定義跳轉的具體的頁面 -->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login.do"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/pages/main.jsp"
/>
<!-- 關閉跨域請求 -->
<security:csrf disabled="true"/>
<!-- 退出 -->
<security:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/login.jsp" />
</security:http>
<!-- 切換成數據庫中的用戶名和密碼 -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<!--
<security:password-encoder ref="passwordEncoder"/>
-->
</security:authentication-provider>
</security:authentication-manager>
<!-- 配置加密類 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!-- 提供了入門的方式,在內存中存入用戶名和密碼
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="{noop}admin" authorities="ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
-->
<security:global-method-security secured-annotations="enabled"/>
</beans>
這個文件是我自己後面進行了修改的,入門的話Spring security框架會提供一個默認的登錄窗口,醜的要死,此處就直接跳過那個部分了,直接使用自己的頁面。
首先我們使用了<security:http pattern/>配置不攔截的靜態資源。
<security:intercept-url pattern="/**" access="ROLE_USER" />該配置代表所有路徑都要ROLE_USER權限,不要丟了前面的ROLE_,這個非常重要
這個便是自定義的相關頁面,相關屬性見名知意,不作過多解釋
接下來便是用戶賬號密碼相關
該配置代表了使用配置文件裏的賬戶名和密碼,此處配置了一個用戶名爲admin,用戶密碼爲admin,具有角色ROLE_USER,關於中間的{noop},代表了加密方式,noop代表不加密,使用明文密碼,這樣我們在登錄的時候便可以直接使用admin這個密碼了,至於{noop}詳細的一些內容後續再說,此處先入門再說。
在實際開發中我們當然是需要根據數據庫的賬戶密碼來進行驗證登錄,以下爲數據庫登錄的配置,被註釋掉的部分是加密方式,此處先不使用這種方式,具體加密方式我們在java代碼中來使用。
配置文件寫完後我們該使用java代碼來進行登錄邏輯的編寫了,而在這些開始之前,我們需要了解一個接口,UserDetails.
該接口的作用是用來封裝當前認證的用戶信息,因爲是一個接口所以我們需要實現該接口,我們也可以使用SpringSecurity框架提供的UserDetails的實現類User來完成相關操作,以下是User的部分代碼。
public class User implements UserDetails, CredentialsContainer {
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired; //帳戶是否過期
private final boolean accountNonLocked; //帳戶是否鎖定
private final boolean credentialsNonExpired; //認證是否過期
private final boolean enabled; //帳戶是否可用
}
而我們則需要使用UserDetailsService這個接口來進行登錄驗證操作,我們點開源碼會發現UserDetailsService接口只有一個方法,loadUserByUsername,該方法最終會返回一個UserDetails,而我們則需要實現這個接口,並封裝UserDetails所需要的相關參數,最後返回該對象即可。
我們迴歸到Spring-Security框架配置文件內,因爲我這裏的登錄操作是在service層中的userService裏,所以我這裏直接使用自定義的登錄驗證。
實現接口後然後重寫loadUserByUsername的方法,返回一個UserDetails。
而在該類裏首先要做的就是根據傳過來的username參數,去數據庫中查詢出來相關信息,並封裝到實體類中,我這裏是一個叫UserInfo的實體類,dao層的數據庫查詢操作這裏就不寫了,很簡單。
然後再將數據庫中查詢出來並封裝好的UserInfo,封裝到User類中,此User類是SpringSeciruty框架的那個,上面有作說明,我這裏先貼上源碼,具體功能對照上圖可以看出來,如下圖所示。
public interface UserDetails extends Serializable {
// ~ Methods
// ========================================================================================================
/**
* Returns the authorities granted to the user. Cannot return <code>null</code>.
*
* @return the authorities, sorted by natural key (never <code>null</code>)
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* Returns the password used to authenticate the user.
*
* @return the password
*/
String getPassword();
/**
* Returns the username used to authenticate the user. Cannot return <code>null</code>.
*
* @return the username (never <code>null</code>)
*/
String getUsername();
/**
* Indicates whether the user's account has expired. An expired account cannot be
* authenticated.
*
* @return <code>true</code> if the user's account is valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
boolean isAccountNonExpired();
/**
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
*
* @return <code>true</code> if the user is not locked, <code>false</code> otherwise
*/
boolean isAccountNonLocked();
/**
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
*
* @return <code>true</code> if the user's credentials are valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
boolean isCredentialsNonExpired();
/**
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
*
* @return <code>true</code> if the user is enabled, <code>false</code> otherwise
*/
boolean isEnabled();
}
先看兩個最主要的屬性,password和username,從UserInfo中取出並封裝進去,然後是剩下幾個boolean類型的參數,我這裏全部選擇true,剩下最後一個。
該方法返回一個集合,我們追蹤下GrantedAuthority會發現這是個接口,而這個接口裏只有一個方法,就是
返回一個String類型的玩意,而getAuthorities是返回一個集合,於是我們自己在Service層實現類中寫上一個getAuthority方法,如下所示:
//返回一個角色描述集合
public List<SimpleGrantedAuthority> getAuthority(List<Role> roles){
List<SimpleGrantedAuthority> list = new ArrayList<SimpleGrantedAuthority>();
for (Role role:roles){
list.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));
}
return list;
}
該方法的作用是返回一個角色描述集合,而該角色是從數據庫根據用戶獲取的,之前有說過ROLE_非常重要,因爲我們在數據庫中存儲的一般都是ADMIN,USER這樣的字符串,所以我們需要拼接一個ROLE_在前面,否則會拋異常,最後將該集合返回。
這樣一來我們的User類就完成了,完工後的User類應該是寫成這樣的。
中間灰色的那個是idea的自動參數提示。
ok,到這一步就剩下之前說的那個坑了,也就是User這個類中的密碼這個參數,前面拼接了一個{bcrypt},這個其實就是一開始使用配置文件的用戶的時候那個{noop},如果我們數據庫使用的賬戶的密碼是明文的,比如admin,他的密碼是123,那麼我們就不能使用{bcrypt}而是應當使用{noop},如果我們保存的密碼是加密後的,則應該使用對應的加密方式,所以接下來我們應當使用Spirng-Security的加密來新建一個賬戶試試,還是一樣直接在Service層操作,Controller層從前端獲取參數並封裝調用Service的代碼還請各位自主完成。
我這裏新建了一個save方法,controller層調用並傳遞一個UserInfo過來,因爲我們是需要對密碼進行加密,所以我這裏需要重新setPassword,首先我們需要將BCryptPasswordEncoder對象拿到
這樣我們就對密碼進行了加密了,加密後的密碼大概就長這樣,第一個123是我直接從數據庫添加的,這個就是明文的密碼,而剩餘的都是加密後的,需要注意的是即使是不同的賬戶設置的密碼是同一個,經過加密後的字符串都是不一樣的。
因爲我們使用了BCryptPasswordEncoder進行加密,所以在使用新建的加密密碼賬戶登錄的時候,我們應當在密碼前面加上{bcrypt},否則登錄不上去,而Spring-Security框架不僅僅只有這一個加密方式,還有比如MD5等多種加密方式,具體的後續再說吧,一樣的,使用不同的加密方式則需要修改{bcrypt}裏面的內容,到此Spring-Security安全框架的基本新建賬戶和數據庫登錄功能就到這裏,裏面還有很多源碼沒有分析,後續有時間再慢慢整理吧。