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(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>