Java框架之SpringSecurity-許可權系統
SpringSecurity
SpringSecurity融合Spring技術棧,提供JavaEE應用的整體安全解決方案;提供全面的安全服務。Spring Security支援廣泛的認證模型
模組劃分
Core - spring-security-core.jar |
核心模組:核心認證、授權功能、支援jdbc-user功能、支援獨立的Spring應用 |
Remoting - spring-security-remoting.jar |
遠端互動模組:一般不需要,可以使用Spring Remoting功能簡化遠端客戶端互動 |
Web - spring-security-web.jar |
web安全模組:web專案使用,基於URL的訪問控制(access-control) |
Config - spring-security-config.jar |
java配置模組:必須依賴包,包含解析xml方式和java 註解方式來使用SpringSecurity功能 |
LDAP - spring-security-ldap.jar |
ldap(輕量目錄訪問協議)支援模組:可選依賴包,LDAP功能支援 |
ACL - spring-security-acl.jar |
ACL支援:ACL(Access-Control-List)訪問控制列表。細粒度的資源訪問控制(RBAC+ACL) |
CAS - spring-security-cas.jar |
CAS整合支援:CAS(Central Authentication Service)中央認證服務。開源ApereoCAS整合 |
OpenID - spring-security-openid.jar |
OpenID 認證方式: 用於針對外部伺服器對使用者進行身份驗證(微信,新浪微博第三方登入) |
Test - spring-security-test.jar |
測試模組:快速的測試SpringSecurity應用 |
基於Maven Web工程例項
新增 security-pom 依賴
<!-- 安全框架中的jar包 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.10.RELEASE</version> </dependency> <!-- 標籤庫 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.10.RELEASE</version> </dependency>
web.xml 中新增 SpringSecurity的 Filter 進行安全控制
<!-- 核心控制器,注意需將spring及springmvc配置檔案都由Web容器裝載 --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:/spring/springmvc.xml classpath*:/spring/spring-*.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!--代理管理所有 SpringSecurity 過濾器--> <filter> <filter-name>springSecurityFilterChain</filter-name><!--名稱固定,不能變--> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
SpringSecurity 配置類
/** * @Configuration 管理程式中的元件(掃描) * @EnableWebSecurity 安全框架支援註解的形式 基礎註解
* @EnableGlobalMethodSecurity 開啟使用表示式方法驗證安全性 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override //認證 protected void configure(AuthenticationManagerBuilder auth) throws Exception { } @Override //授權 protected void configure(HttpSecurity http) throws Exception { } }
檢視登入頁面的原始碼,有個標籤<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> 這是SpringSecurity 幫我們防止“跨站請求偽造”攻擊;還可以防止表單重複提交。此標籤 value 值會動態生成一個令牌值當用戶請求登入時會驗證此令牌值的正確性。如果想禁用此功能可在配置類中設定 http.csrf().disable();
l 令牌值變化:
n 如果登入成功(使用者名稱,密碼正確),令牌會被刪除,
n 重新回到登入頁或後退網頁,令牌會重新生成;
n 如果登入失敗(使用者名稱,密碼錯誤),令牌不變。
n 重新整理登入頁,令牌值也不變
認證
@Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //從資料庫中查詢資料 auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); }
UserDetailsService
此介面由 Security 來呼叫基於 AOP 模式進行許可權檢查,返回一個 UserDetails 介面型別其存放著該使用者從資料庫查詢出來的所有許可權資訊
步驟:
1 在業務層實現 UserDetailsService 介面通過使用者名稱從 Dao 層查詢出該使用者物件
2 建立一個 HashSet<GrantedAuthority> 介面型別的集合,該 GrantedAuthority 型別用來存放角色和許可權資訊,它的實現類 SimpleGrantedAuthority 需要傳入字串型別角色名和許可權名
3 通過該使用者 id 查詢出該使用者所擁有的角色集合
4 通過該使用者 id 查詢出該使用者所擁有的許可權集合
5 通過所有角色名和所有許可權名 建立 SimpleGrantedAuthority 物件並新增到 HashSet<GrantedAuthority> 集合中
6 建立 User 類物件,該物件實現了 UserDetails 介面,為此 user 物件傳入該使用者的使用者名稱和密碼加上許可權集合 Set 並返回該物件即可
@Autowired //使用者 private TAdminMapper adminMapper; @Autowired //角色 private TRoleMapper roleMapper; @Autowired //許可權 private TPermissionMapper permissionMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { TAdminExample example = new TAdminExample(); TAdminExample.Criteria criteria = example.createCriteria(); criteria.andLoginacctEqualTo(username); List<TAdmin> admins = adminMapper.selectByExample(example); //從資料庫中查出該使用者 TAdmin admin = admins.get(0); //該集合用來存放角色和許可權 HashSet<GrantedAuthority> authorities = new HashSet<>(); //從資料庫中查出該使用者所對應的角色 List<TRole> roles = roleMapper.listRole(admin.getId()); //從資料庫中查出該使用者所對應的許可權 List<TPermission> permissions = permissionMapper.listPermission(admin.getId()); //分別將角色和許可權新增到 authorities 集合中 for (TRole role : roles) { String name = role.getName(); authorities.add(new SimpleGrantedAuthority("ROLE_" + name));//注意角色需加上 "ROLE_" } permissions.forEach((p) -> { String name = p.getName(); authorities.add(new SimpleGrantedAuthority(name)); }); //通過該使用者名稱和密碼以及許可權集合建立User物件並返回 User user = new User(admin.getLoginacct().toString(), admin.getUserpswd().toString(), authorities); return user; }
授權
HttpSecurity 該類允許對特定的http請求基於安全考慮進行配置。預設情況下,適用於所有的請求.亦通過該物件 http 方法為使用者配置精細化許可權訪問控制
@Override protected void configure(HttpSecurity http) throws Exception { //基於httpRequest對指定antMatchers資源permitAll放行,對於其他請求anyRequest必須通過認證authenticated http.authorizeRequests().antMatchers("/welcome.jsp","/static/**") .permitAll().anyRequest().authenticated(); //跳轉到預設登入介面 http.formLogin().loginPage("/welcome.jsp"); //登入時指定的控制器/login,並驗證使用者名稱和密碼,成功驗證後跳轉到控制器/main http.formLogin().loginProcessingUrl("/login") .usernameParameter("loginacct") .passwordParameter("userpswd") .defaultSuccessUrl("/main"); //取消csrf令牌值驗證 http.csrf().disable(); //退出時指定的控制器,並指定成功退出後登入介面 http.logout().logoutUrl("/exit").logoutSuccessUrl("/welcome.jsp"); //記住我功能.需在前端複選框中指定 value 值為 remember-me http.rememberMe(); //自定義的異常處理器 http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { //判斷是否為非同步請求 if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){ response.getWriter().write("403"); }else { request.setAttribute("msg",accessDeniedException.getMessage()); request.getRequestDispatcher("/WEB-INF/views/unauth.jsp") .forward(request,response); } } }); }
通過方法呼叫可以更精細化控制訪問許可權
authorizeRequests():返回一個配置物件用於配置請求的訪問限制
formLogin():返回表單配置物件,當什麼都不指定時會提供一個預設的,如配置登入請求,還有登入成功頁面
logout():返回登出配置物件,可通過logoutUrl設定退出url
antMatchers:匹配請求路徑或請求動作型別,如:.antMatchers("/admin/**")
addFilterBefore: 在某過濾器之前新增 filter
addFilterAfter:在某過濾器之後新增 filter
addFilterAt:在某過濾器相同位置新增 filter,不會覆蓋相同位置的 filter
hasRole:結合 antMatchers 一起使用,設定請求允許訪問的角色許可權或IP
方法名 |
用途 |
access(String) |
SpringEL表示式結果為true時可訪問 |
anonymous() |
匿名可訪問 |
denyAll() |
使用者不可以訪問 |
fullyAuthenticated() |
使用者完全認證訪問(非remember me下自動登入) |
hasAnyAuthority(String…) |
引數中任意許可權可訪問 |
hasAnyRole(String…) |
引數中任意角色可訪問 |
hasAuthority(String) |
某一許可權的使用者可訪問 |
hasRole(String) |
某一角色的使用者可訪問 |
permitAll() |
所有使用者可訪問 |
rememberMe() |
允許通過remember me登入的使用者訪問 |
authenticated() |
使用者登入後可訪問 |
hasIpAddress(String) |
使用者來自引數中的IP可訪問 |
@EnableGlobalMethodSecurity詳解
@EnableGlobalMethodSecurity(securedEnabled=true) 開啟@Secured 註解過濾許可權
@Secured("軟體工程師") :擁有指定角色才可以訪問方法
@EnableGlobalMethodSecurity(jsr250Enabled=true)開啟@RolesAllowed 註解過濾許可權
@EnableGlobalMethodSecurity(prePostEnabled=true) 使用 SpEL 表示式方法級別的安全性 4個註解可用
@PreAuthorize 在方法執行之前檢查,基於表示式的計算結果來限制對方法的訪問 //@PreAuthorize("hasRole('軟體工程師')")
@PostAuthorize 在方法執行後檢查,但是如果表示式計算結果為false,將丟擲一個安全性異常
@PostFilter 允許方法呼叫,但必須按照表達式來過濾方法的結果
@PreFilter 允許方法呼叫,但必須在進入方法之前過濾輸入值
Security 標籤
在 jsp 頁面還可通過標籤進一步控制 html 標籤的訪問許可權或獲取該使用者資訊 : 引入標籤庫
<%@taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <sec:authentication property="name"/> //在需要位置顯示使用者登入名, 屬性 property 必須是 name <sec:authorize access="hasRole('PM - 專案經理')">//非此角色使用者隱藏下面的標籤 <button type="button" id="deleteBath" class="btn btn-danger" style="float:right;margin-left:10px;">刪除</button> </sec:authorize>
&n