寫這篇文章是因爲我做了一個電商網站項目,近期剛加上權限控制。整個過程很簡單,在此給大家梳理一下,也算是自己對知識點的一個總結。
一、需求分析:
我們都知道,電商網站在權限這一塊,有兩大塊內容:
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相反,如果沒有交集即可
歡迎大家提出寶貴的修改意見,感謝!