springBoot(23):spring-security-
一、簡介
Web應用的安全管理,主要包括兩個方面的內容:身份認證、用戶授權,此處使用spring-cloud-security來說明。
二、依賴管理
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency>
三、安全策略配置
Spring Security已經大體實現了,我們這裏只是需要一些配置與引用。
package com.example.demo.config; import com.example.demo.utils.security.CustomUserService; import com.example.demo.utils.security.LoginSuccessHandler; import com.example.demo.utils.security.MyFilterSecurityInterceptor; import com.example.demo.utils.security.SecuritySettings; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; /** * Security安全配置 * * @Author: 我愛大金子 * @Description: Security安全配置 * @Date: Create in 15:20 2017/7/5 */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomFilterSecurityInterceptor customFilterSecurityInterceptor; // 權限管理過濾器 @Autowired private SecuritySettings securitySettings; // 自定義安全配置類 /**註冊UserDetailsService的bean*/ @Bean public UserDetailsService customUserService(){ return new CustomUserService(); } /**登錄認證*/ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserService()); //userDetailsService驗證 } /***設置不攔截規則*/ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/images/**", "/druid/**"); } /**安全策略配置*/ @Override protected void configure(HttpSecurity http) throws Exception { // 設置遊客可以訪問的URI if (StringUtils.isNotBlank(securitySettings.getPermitall())) { http.authorizeRequests().antMatchers(securitySettings.getPermitall().split(",")).permitAll(); } http.authorizeRequests() .anyRequest().authenticated() //任何請求,登錄後可以訪問 // 配置登錄URI、登錄失敗跳轉URI與登錄成功後默認跳轉URI .and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll().defaultSuccessUrl("/", true).successHandler(loginSuccessHandler()) // 註銷行為任意訪問 .and().logout().permitAll() // 設置拒絕訪問的提示URI .and().exceptionHandling().accessDeniedPage("/login?illegal") ; http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class); } /**登錄成功處理器*/ private AuthenticationSuccessHandler loginSuccessHandler() { return new LoginSuccessHandler(); } }
說明:
loginPage:設置一個實驗自定義的登錄URI
loginSuccessHandler:設置自定義的登錄處理器
permitAll:是允許訪問
accessDeniedPage:配置拒絕訪問的提示URI
antMatchers:對URI的配置
假設我要管理員才可以訪問admin文件夾下的內容,如:.antMatchers("/admin/**").hasRole("ROLE_ADMIN"),
也可以設置admin文件夾下的文件可以有多個角色來訪問,如:.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
也可以通過hasIpAddress來指定某一個ip可以訪問該資源,寫法如下.antMatchers("/admin/**").hasIpAddress("210.210.210.210")
3.1、自定義安全配置類
為是更方便的使用springSecurity,我們自定義一個權限的配置類,如配置登錄的URI、遊客訪問的URI等配置項
package com.example.demo.utils.security; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * 自定義安全配置類 * * @Author: 我愛大金子 * @Description: 自定義安全配置類 * @Date: Create in 9:45 2017/7/6 */ @Configuration @ConfigurationProperties(prefix = "securityConfig") public class SecuritySettings { /**允許訪問的URL,多個用逗號分隔*/ private String permitall; public String getPermitall() { return permitall; } public void setPermitall(String permitall) { this.permitall = permitall; } }
3.2、登錄成功處理器
登錄成功後,如果需要對用戶的行為做一些記錄或者執行其它操作,則可以使用登錄成功處理器。
package com.example.demo.utils.security; import com.example.demo.pojo.SysUser; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 登錄成功處理器 * * @Author: 我愛大金子 * @Description: 登錄成功處理器 * @Date: Create in 11:35 2017/7/6 */ public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { User userDetails = (User) authentication.getPrincipal(); System.out.println("登錄用戶:username=" + userDetails.getUsername() + ", uri=" + request.getContextPath()); super.onAuthenticationSuccess(request, response, authentication); } }
3.3、springMVC 配置(訪問 /login 轉向 login.html 頁面)
package com.example.demo.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * springMVC 配置(註冊訪問 /login 轉向 login.html 頁面) * * @Author: 我愛大金子 * @Description: springMVC 配置(註冊訪問 /login 轉向 login.html 頁面) * @Date: Create in 16:24 2017/7/5 */ @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); } }
四、登錄認證
在安全策略配置代碼中有,主要看自定義的CustomUserService,此類實現了UserDetailsService接口,重寫了loadUserByUsername方法
package com.example.demo.utils.security; import com.example.demo.dao.SysPermissionDao; import com.example.demo.dao.SysUserDao; import com.example.demo.pojo.SysPermission; import org.springframework.security.core.userdetails.User; import com.example.demo.pojo.SysUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.ArrayList; import java.util.List; /** * 自定義UserDetailsService,將用戶權限交給springsecurity進行管控 * * @Author: 我愛大金子 * @Description: 將用戶權限交給Springsecurity進行管控 * @Date: Create in 16:19 2017/7/5 */ public class CustomUserService implements UserDetailsService { @Autowired private SysUserDao sysUserDao; @Autowired private SysPermissionDao sysPermissionDao; @Override public UserDetails loadUserByUsername(String username) { SysUser user = sysUserDao.findByUserName(username); if (user != null) { List<SysPermission> permissions = sysPermissionDao.findByAdminUserId(user.getId()); List<GrantedAuthority> grantedAuthorities = new ArrayList <>(); for (SysPermission permission : permissions) { if (permission != null && permission.getName()!=null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName()); //1:此處將權限信息添加到 GrantedAuthority 對象中,在後面進行全權限驗證時會使用GrantedAuthority 對象。 grantedAuthorities.add(grantedAuthority); } } return new User(user.getUsername(), user.getPassword(), grantedAuthorities); } else { throw new UsernameNotFoundException("admin: " + username + " do not exist!"); } } }
五、權限管理
在Security安全配置類中使用了權限管理過濾器CustomFilterSecurityInterceptor
package com.example.demo.utils.security; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.stereotype.Service; import java.io.IOException; /** * 權限管理過濾器 * * @Author: 我愛大金子 * @Description: 權限管理過濾器 * @Date: Create in 17:16 2017/7/5 */ @Service public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { @Autowired private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; // 權限配置資源管理器 /**權限管理決斷器*/ @Autowired public void setMyAccessDecisionManager(CustomAccessDecisionManager customAccessDecisionManager) { super.setAccessDecisionManager(customAccessDecisionManager); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { //fi裏面有一個被攔截的url //裏面調用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有權限 //再調用MyAccessDecisionManager的decide方法來校驗用戶的權限是否足夠 InterceptorStatusToken token = super.beforeInvocation(fi); try { //執行下一個攔截器 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } @Override public void destroy() { } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.customFilterInvocationSecurityMetadataSource; } }
說明:
customFilterSecurityInterceptor:權限管理過濾器
customAccessDecisionManager:權限管理決斷器
customFilterInvocationSecurityMetadataSource:權限配置資源管理器
其中過濾器在系統啟動時開始工作,並同時導入權限配置資源管理器和權限管理決斷器,對用戶訪問的資源進行管理。權限管理決斷器對用戶訪問的資源與用戶擁有的角色權限進行對比,以此來判斷用戶是否對某個資源具有訪問權限。
5.1、權限管理過濾器
繼承與AbstractSecurityInterceptor,實時監控用戶的行為,防止用戶訪問未被授權的資源。
package com.example.demo.utils.security; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.stereotype.Service; import java.io.IOException; /** * 權限管理過濾器 * * @Author: 我愛大金子 * @Description: 權限管理過濾器 * @Date: Create in 17:16 2017/7/5 */ @Service public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { Logger log = LoggerFactory.getLogger(CustomFilterSecurityInterceptor.class); @Autowired private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; // 權限配置資源管理器 /**權限管理決斷器*/ @Autowired public void setMyAccessDecisionManager(CustomAccessDecisionManager customAccessDecisionManager) { super.setAccessDecisionManager(customAccessDecisionManager); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); log.info("【權限管理過濾器】請求URL:" + fi.getRequestUrl()); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { //fi裏面有一個被攔截的url //裏面調用CustomFilterInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有權限 //再調用CustomAccessDecisionManager的decide方法來校驗用戶的權限是否足夠 InterceptorStatusToken token = super.beforeInvocation(fi); try { //執行下一個攔截器 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } catch(Exception e) { log.error("【權限管理過濾器】【異常】" + e.getMessage(), e); } finally { super.afterInvocation(token, null); } } @Override public void destroy() { } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.customFilterInvocationSecurityMetadataSource; } }
5.2、權限管理決斷器
權限管理的關鍵部分就是決斷器,它實現了AccessDecisionManager,重寫了decide方法,使用自定義的決斷器,在用戶訪問受保護的資源時,決斷器判斷用戶擁有的角色中是否對改資源具有訪問權限,如果沒有,則拒絕訪問
package com.example.demo.utils.security; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Iterator; /** * 權限管理決斷器 * * @Author: 我愛大金子 * @Description: 權限管理決斷器 * @Date: Create in 17:15 2017/7/5 */ @Service public class CustomAccessDecisionManager implements AccessDecisionManager { Logger log = LoggerFactory.getLogger(CustomAccessDecisionManager.class); // decide 方法是判定是否擁有權限的決策方法, //authentication 是釋CustomUserService中循環添加到 GrantedAuthority 對象中的權限信息集合. //object 包含客戶端發起的請求的requset信息,可轉換為 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); //configAttributes 為MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法返回的結果,此方法是為了判定用戶請求的url 是否在權限表中,如果在權限表中,則返回給 decide 方法,用來判定用戶是否有此權限。如果不在權限表中則放行。 @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(null== configAttributes || configAttributes.size() <=0) { return; } ConfigAttribute c; String needRole; for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) { c = iter.next(); needRole = c.getAttribute(); for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 為在註釋1 中循環添加到 GrantedAuthority 對象中的權限信息集合 if(needRole.trim().equals(ga.getAuthority())) { return; } } log.info("【權限管理決斷器】需要role:" + needRole); } throw new AccessDeniedException("Access is denied"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
5.3、權限配置資源管理器
權限配置資源管理器實現了FilterInvocationSecurityMetadataSource,在啟動時就去加載了所有的權限列表,權限配置資源管理器為決斷器實時提供支持,判斷用戶訪問的資源是否在受保護的範圍之內。
package com.example.demo.utils.security; import com.example.demo.dao.SysPermissionDao; import com.example.demo.pojo.SysPermission; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.util.*; /** * 權限配置資源管理器 * * @Author: 我愛大金子 * @Description: 權限配置資源管理器 * @Date: Create in 17:17 2017/7/5 */ @Service public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired private SysPermissionDao sysPermissionDao; private HashMap<String, Collection<ConfigAttribute>> map =null; /** * 加載權限表中所有權限 */ public void loadResourceDefine(){ map = new HashMap<>(); Collection<ConfigAttribute> array; ConfigAttribute cfg; List<SysPermission> permissions = sysPermissionDao.findAll(); for(SysPermission permission : permissions) { array = new ArrayList<>(); cfg = new SecurityConfig(permission.getName()); //此處只添加了用戶的名字,其實還可以添加更多權限的信息,例如請求方法到ConfigAttribute的集合中去。此處添加的信息將會作為MyAccessDecisionManager類的decide的第三個參數。 array.add(cfg); //用權限的getUrl() 作為map的key,用ConfigAttribute的集合作為 value, map.put(permission.getUrl(), array); } } //此方法是為了判定用戶請求的url 是否在權限表中,如果在權限表中,則返回給 decide 方法,用來判定用戶是否有此權限。如果不在權限表中則放行。 @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { if(map ==null) loadResourceDefine(); //object 中包含用戶請求的request 信息 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); AntPathRequestMatcher matcher; String resUrl; for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) { resUrl = iter.next(); matcher = new AntPathRequestMatcher(resUrl); if(matcher.matches(request)) { return map.get(resUrl); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } }
六、根據權限設置連接
對於權限管理,我們可能希望,在一個用戶訪問的界面中,不是等到用戶點擊了超鏈接之後,才來判斷用戶有沒有這個權限,而是按照用戶擁有的權限來顯示超鏈接。這樣的設計對於用戶體驗來說,會更友好。
6.1、方法1:使用sec標簽(thymeleaf)
在html標簽中引入的Spring Security的標簽:
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
sec:authentication="name":取當前登錄用戶的用戶名
<title sec:authentication="name"></title>
sec:authorize="hasRole(‘ROLE_ADMIN‘):表示當前用戶是否擁有角色ROLE_ADMIN
<li sec:authorize="hasRole(‘ROLE_ADMIN‘)"><a th:href="@{/admin}"> admin </a></li>
本文出自 “我愛大金子” 博客,請務必保留此出處http://1754966750.blog.51cto.com/7455444/1945094
springBoot(23):spring-security-