Spring Security 整合freemaker 實現簡單登入和角色控制
寫這篇文章是因為我做了一個電商網站專案,近期剛加上許可權控制。整個過程很簡單,在此給大家梳理一下,也算是自己對知識點的一個總結。
一、需求分析:
我們都知道,電商網站在許可權這一塊,有兩大塊內容:
1、使用者未登入,部分頁面拒絕訪問(如:下訂單)
2、不同角色使用者登入看到的功能模組不一樣(如:買家、賣家、客服等)
基於以上需求,接下來我們要解決的就是對使用者登入的攔截以及對許可權和角色的控制。
二、專案環境說明:
使用SSM(SpringMVC+Spring+Mybatis)框架,mysql資料庫、maven專案管理工具,freemaker前端引擎。對以上又不懂的朋友們可以自己去百度瞭解,這裡就廢話不多說了。
三、前期儲備知識(如果對Spring Security很熟悉的可以跳過此步)
Security框架可以精確控制頁面的一個按鈕、連結,它在頁面上許可權的控制實際上是通過它提供的標籤來做到的。
-
簡介
一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方式的安全框架(簡單說是對訪問許可權進行控制嘛),應用的安全性包括使用者認證(Authentication)和使用者授權(Authorization)兩個部分。使用者認證指的是驗證某個使用者是否為系統中的合法主體,也就是說使用者能否訪問該系統。使用者認證一般要求使用者提供使用者名稱和密碼。系統通過校驗使用者名稱和密碼來完成認證過程。使用者授權指的是驗證某個使用者是否有許可權執行某個操作。在一個系統中,不同使用者所具有的許可權是不同的。比如對一個檔案來說,有的使用者只能進行讀取,而有的使用者可以進行修改。一般來說,系統會為不同的使用者分配不同的角色,而每個角色則對應一系列的許可權。spring security的主要核心功能為認證和授權,所有的架構(如:Shiro安全框架)也是基於這兩個核心功能去實現的。
-
框架原理
眾所周知 想要對對Web資源進行保護,最好的辦法莫過於Filter,要想對方法呼叫進行保護,最好的辦法莫過於AOP。所以springSecurity在我們進行使用者認證以及授予許可權的時候,通過各種各樣的攔截器來控制權限的訪問,從而實現安全。
如下為其主要過濾器 :
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
UsernamePasswordAuthenticationFilter
BasicAuthenticationFilter
-
框架的核心元件
SecurityContextHolder:提供對SecurityContext的訪問
SecurityContext,:持有Authentication物件和其他可能需要的資訊
AuthenticationManager 其中可以包含多個AuthenticationProvider
ProviderManager物件為AuthenticationManager介面的實現類
AuthenticationProvider 主要用來進行認證操作的類 呼叫其中的authenticate()方法去進行認證操作
Authentication:Spring Security方式的認證主體
GrantedAuthority:對認證主題的應用層面的授權,含當前使用者的許可權資訊,通常使用角色表示
UserDetails:構建Authentication物件必須的資訊,可以自定義,可能需要訪問DB得到
UserDetailsService:通過username構建UserDetails物件,通過loadUserByUsername根據userName獲取UserDetail物件
以上知識點來源於部落格:springSecurity安全框架的學習和原理解讀
四、開始實戰:
- 在pom.xml檔案中加入Security 座標:
<!--spring security 依賴包-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
- 在web.xml中配置Security
<!--配置Spring Security-->
<!--filter的宣告-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<!--mapping就是filter的對映,就是哪些檔案用到這個filter-->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- Controller檔案程式碼 (SecurityConfig.java)
@Configuration
@EnableWebSecurity
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailService userDetailService;
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()
.antMatchers("/index/show").hasAnyRole("ADMIN","BUYER","SELLER")//個人首頁只允許擁有ADMIN,BUYER,SELLER角色的使用者訪問
.antMatchers("/cart/show").hasAnyRole("ADMIN","MAIJIA","SELLER")
//在此後面可以根據自己的專案需要進行頁面攔截的新增
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/index/login").permitAll()//這裡程式預設路徑就是登陸頁面,允許所有人進行登陸
.loginProcessingUrl("/j_spring_security_check")//登陸提交的處理url
.usernameParameter("j_username")//登陸使用者名稱引數
.passwordParameter("j_password")//登陸密碼引數
.failureUrl("/index/login?error=true")//登陸失敗進行轉發,這裡回到登陸頁面,引數error可以告知登陸狀態
.defaultSuccessUrl("/index/show")//登陸成功的url,這裡去到個人首頁
.and().logout().logoutUrl("/j_spring_security_logout").permitAll().logoutSuccessUrl("/index/login?logout=true")//按順序,第一個是登出的url,security會攔截這個url進行處理,所以登出不需要我們實現,第二個是登出url,logout告知登陸狀態
.and()
.addFilter(myUsernamePasswordAuthenticationFilter)
.rememberMe()
.tokenValiditySeconds(604800)//記住我功能,cookies有限期是一週
.and()
.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception{
super.configure(web);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailService);
}
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
- 接下來是Service層的程式碼(UserDetailService.java)
@Service
public class UserDetailService implements UserDetailsService {
@Autowired
private UserDetailsDao userDetailsDao;
/**
* 獲取所屬角色
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查出使用者名稱、密碼、角色資訊
Users users = userDetailsDao.getUserByName(username);
if (users==null) {
throw new UsernameNotFoundException("找不到該賬戶資訊!");
}
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>(); //GrantedAuthority是security提供的許可權類,
list.add(new SimpleGrantedAuthority("ROLE_"+users.getRoles()));
User auth_user = new User(users.getUsername(), users.getPassword(), list);
return auth_user;
}
}
- 前端程式碼 .ftl檔案(在此只粘出一部分程式碼,只顯示一下用法)
在ftl檔案頭部加上,引入Security檔案
<#assign sec=JspTaglibs["http://www.springframework.org/security/tags"]/>
<#--沒有登入時 能看到買家、賣家所有資訊-->
<@sec.authorize ifNotGranted="ROLE_ADMIN,ROLE_MAIJIA,ROLE_SELLER,ROLE_BOTHSM">
<dl>
<dt><a href="/jt/success/orderlist?page=1&orderPeriod=threemonth&orderType=allorder">買家中心</a></dt>
<dd>
<a href="/jt/success/orderlist?page=1&orderPeriod=threemonth&orderType=allorder">全部訂單</a>
<a href="#">優惠券</a> <br />
<a href="/jt/cart/show" class="shoppingCart">我的購物車</a>
<a href="/jt/favorite/myfavorites/1/time">我的收藏</a>
</dd>
</dl>
<dl>
<dt><a href="/jt/sellercenter/allproduct?page=1&fenleiId=allproduct">賣家中心</a> </dt>
<dd>
<a href="/jt/seller/soldprolist?page=1&orderPeriod=threemonth&orderType=allorder">已賣出貨品</a>
<a href="/jt/release/release1">釋出供應產品</a><br />
<a href="/jt/sellercenter/allproduct?page=1&fenleiId=allproduct">管理供應產品</a>
<a href="/jt/news/toNewsReleaseListSeller?currentPage=1&newsStatus=2">釋出公告</a>
</dd>
</dl>
</@sec.authorize>
<#--登陸後買家中心 只有以買家身份登入可以看到-->
<@sec.authorize ifAnyGranted="ROLE_MAIJIA">
<dl>
<dt><a href="/jt/success/orderlist?page=1&orderPeriod=threemonth&orderType=allorder">買家中心</a></dt>
<dd>
<a href="/jt/success/orderlist?page=1&orderPeriod=threemonth&orderType=allorder">全部訂單</a>
<a href="#">優惠券</a> <br />
<a href="/jt/cart/show" class="shoppingCart">我的購物車</a>
<a href="/jt/favorite/myfavorites/1/time">我的收藏</a>
</dd>
</dl>
</@sec.authorize>
<#--登陸後賣家中心 只有以賣家身份登入可以看到-->
<@sec.authorize ifAnyGranted="ROLE_SELLER">
<dl>
<dt><a href="/lbt/sellercenter/allproduct?page=1&fenleiId=allproduct">賣家中心</a> </dt>
<dd>
<a href="/lbt/seller/soldprolist?page=1&orderPeriod=threemonth&orderType=allorder">已賣出貨品</a>
<a href="/lbt/release/release1">釋出供應產品</a><br />
<a href="/lbt/sellercenter/allproduct?page=1&fenleiId=allproduct">管理供應產品</a>
<a href="/lbt/news/toNewsReleaseListSeller?currentPage=1&newsStatus=2">釋出公告</a>
</dd>
</dl>
</@sec.authorize>
五、附加知識點:
頁面標籤的使用與許可權配置相對應
authorize標籤判斷順序是: access->url->ifNotGranted->ifAllGranted->ifAnyGranted
但他們的關係是“與”: 即只要其中任何一個屬性不滿足則該標籤中間的內容將不會顯示給使用者,舉個例子:
<sec:authorize ifAllGranted=”ROLE_ADMIN,ROLE_MEMBER” ifNotGranted=”ROLE_SUPER”>滿足才會顯示給使用者 </sec:authorize>
標籤中間的內容只有在當前使用者擁有ADMIN,MEMBER角色,但不擁有SUPER許可權時才會顯示!
access屬性是基於角色判斷,url屬性是基於訪問路徑判斷。
對於ifAllGranted ,ifNotGranted,ifAnyGranted屬性的理解可以與集合api類比
Collection grantedAuths :當前使用者擁有的許可權
Collection requiredAuths : 當前要求的許可權,即ifAllGranted ,ifNotGranted,ifAnyGranted 屬性的值滿足ifAllGranted: 只需要grantedAuths.containsAll(requiredAuths);返回true即可
滿足ifAnyGranted: 只需要grantedAuths.retainAll(requiredAuths);有內容即可(兩集合有交集)
滿足ifNotGranted:與Any相反,如果沒有交集即可
歡迎大家提出寶貴的修改意見,感謝!