Srping Security網上也有不少例子,但基本都是所資源直接配置在XML文件裏,限制太大,不夠靈活。咱們須要的是能夠在後臺修改資源訪問權限,實時生效,才能符合如今大多數系統的需求。css
<!-- Spring security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.2.RELEASE</version> </dependency> <!--Spring Security end-->
咱們自定義一個實現類MUserDetailsService 來實現UserDetailsService接口。html
其中須要實現一個loadUserByUsername方法,用來讀取用戶的角色。
在這裏須要從數據庫中經過用戶名來查詢用戶的信息和用戶所屬的角色
其中MGrantedAuthority實現了GrantedAuthority接口,用於構建用戶權限。
MUserDeatils實現了UserDeatils接口,用於存放用戶信息與權限java
Spring Security中進行身份驗證的是AuthenticationManager接口,ProviderManager是它的一個默認實現,但它並不用來處理身份認證,而是委託給配置好的AuthenticationProvider,每一個AuthenticationProvider會輪流檢查身份認證。檢查後或者返回Authentication對象或者拋出異常。驗證身份就是加載響應的UserDetails,看看是否和用戶輸入的帳號、密碼、權限等信息匹配。此步驟由實現AuthenticationProvider的DaoAuthenticationProvider(它利用UserDetailsService驗證用戶名、密碼和受權)處理。包含 GrantedAuthority 的 UserDetails對象在構建 Authentication對象時填入數據。web
/** * 根據用戶名加載用戶密碼與權限信息 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查詢用戶信息 User user = userMapper.selectByName(username); List<Role> roleList = null; MUserDeatils userDeatils = null; if (user != null){ //查詢用戶的角色 roleList = roleMapper.queryByUser(user.getId()); System.out.println("user" + user.getUsername() + "----" + user.getPassword()); // 構建權限 Set<MGrantedAuthority> authorities = new HashSet<MGrantedAuthority>(); if (roleList.size() != 0){ for (Role role: roleList){ authorities.add(new MGrantedAuthority(role.getName())); System.out.println(role.getName()); } userDeatils = new MUserDeatils(user.getUsername(),user.getPassword(),authorities); } } return userDeatils; }
public class MGrantedAuthority implements GrantedAuthority { private String authority; public MGrantedAuthority(String authority){ this.authority = authority; } @Override public String getAuthority() { return authority; } }
實現UserDetails接口定義好變量便可spring
須要自定義實現類實現FilterInvocationSecurityMetadataSource接口。經過loadResourceDefine方法能夠實現資源與權限的對應關係。數據庫
要使咱們自定義的MFilterInvocationSecurityMetadataSource生效,咱們還須要定義一個MyFilterSecurityInterceptor類。
這裏的數據須要從數據庫中取得。另外自定義接口UrlMatcher,實現類爲AntUrlPathMatcher。瀏覽器
網上有教程是把loadResourceDefine方法放在了構造函數裏。但我通過屢次試驗均出現service,mapper沒法注入的問題,而後就會報一個空指針的導異常,經debug發現是service沒有注入。經屢次查詢得知:緣由是構造方法會先於注入執行,因此loadResourceDefine方法放入構造中執行時函數內的service與mapper還未執行注入,所以報 java.lang.NullPointerException的異常。解決方法是將loadResourceDefine方法放在getAttributes方法中執行。restful
@Component public class MFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired public IRescAndRoleService iRescAndRoleService ; @Autowired private IUserService iUserService ; private UrlMatcher urlMatcher = new AntUrlPathMatcher(); // 資源權限集合 private static Map<String, Collection<ConfigAttribute>> resourceMap = null; public void loadResourceDefine(){ resourceMap = new HashMap<String, Collection<ConfigAttribute>>(); //取得用戶信息 List<User> userList = iUserService.query(); //取得資源與角色列表 List<RescAndRole> resourceList = iRescAndRoleService.query(); System.out.println(resourceList); for (RescAndRole resource : resourceList) { Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>(); atts.add(new SecurityConfig(resource.getRoleName() )); resourceMap.put(resource.getResString(), atts); } } @Override public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { loadResourceDefine();//防止沒法注入問題 // guess object is a URL. String url = ((FilterInvocation) o).getRequestUrl(); Iterator<String> ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (urlMatcher.pathMatchesUrl(resURL, url)) { return resourceMap.get(resURL); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return true; } }
public class AntUrlPathMatcher implements UrlMatcher { private boolean requiresLowerCaseUrl; private PathMatcher pathMatcher; public AntUrlPathMatcher() { this(true); } public AntUrlPathMatcher(boolean requiresLowerCaseUrl) { this.requiresLowerCaseUrl = true; this.pathMatcher = new AntPathMatcher(); this.requiresLowerCaseUrl = requiresLowerCaseUrl; } public Object compile(String path) { if (this.requiresLowerCaseUrl) { return path.toLowerCase(); } return path; } public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl) { this.requiresLowerCaseUrl = requiresLowerCaseUrl; } public boolean pathMatchesUrl(Object path, String url) { if (("/**".equals(path)) || ("**".equals(path))) { return true; } return this.pathMatcher.match((String) path, url); } public String getUniversalMatchPattern() { return "/**"; } public boolean requiresLowerCaseUrl() { return this.requiresLowerCaseUrl; } public String toString() { return super.getClass().getName() + "[requiresLowerCase='" + this.requiresLowerCaseUrl + "']"; } }
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } public Class<? extends Object> getSecureObjectClass() { return FilterInvocation.class; } public void invoke(FilterInvocation fi) throws IOException, ServletException { InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource newSource) { this.securityMetadataSource = newSource; } public void destroy() { } public void init(FilterConfig arg0) throws ServletException { } }
自定義一個決策管理器MyAccessDecisionManager實現AccessDecisionManager接口。其中的decide方法,決定某一個用戶是否有權限訪問某個urlsession
/* (non-Javadoc) * @see org.springframework.security.access.AccessDecisionManager#decide(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection) * 該方法決定該權限是否有權限訪問該資源,其實object就是一個資源的地址,authentication是當前用戶的 * 對應權限,若是沒登錄就爲遊客,登錄了就是該用戶對應的權限 */ @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(configAttributes == null) { return; } System.out.println(object.toString()); // object is a URL. //所請求的資源擁有的權限(一個資源對多個權限) Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while(iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); //訪問所請求資源所須要的權限 String needPermission = configAttribute.getAttribute(); System.out.println("訪問"+object.toString()+"須要的權限是:" + needPermission); //用戶所擁有的權限authentication Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for(GrantedAuthority ga : authorities) { if(needPermission.equals(ga.getAuthority())) { return; } } } //沒有權限 throw new AccessDeniedException(" 沒有權限訪問! "); } @Override public boolean supports(ConfigAttribute attribute) { // TODO Auto-generated method stub return true; } @Override public boolean supports(Class<?> clazz) { // TODO Auto-generated method stub return true; }
添加Seucrity的過濾器,將攔截全部資源訪問mybatis
只能配置成 /*
<!--加載Security配置文件與mybatis配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF/config/security.xml WEB-INF/config/spring-mybatis.xml </param-value> </context-param> <!-- spring security 的過濾器配置 --> <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>
<b:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!--登錄頁面不驗證--> <http pattern="/userLogin.html" security="none" /> <!--靜態文件請求不驗證--> <http pattern="/js/**" security="none" /> <http pattern="/css/**" security="none" /> <!--restful請求--> <http pattern="/login" security="none" /> <http pattern="/getGrid" security="none" /> <!--瀏覽器會自動請求網站圖標:favicon.ico -不驗證 --> <http pattern="/favicon.ico" security="none" /> <http > <!--自定義權限不足時顯示的頁面--> <access-denied-handler error-page="/accessHint.html"></access-denied-handler> <!-- 自定義登陸界面 --> <form-login authentication-failure-url="/userLogin.html?error=true" login-page="/userLogin.html" default-target-url="/index.html" login-processing-url="/j_spring_security_check" /> <logout invalidate-session="true" logout-success-url="/userLogin.html" logout-url="/j_spring_security_logout"/> <!-- 經過配置custom-filter來增長過濾器,before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默認的過濾器以前執行。 --> <custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" /> <csrf disabled="true" /> </http> <!-- 認證過濾器 --> <b:bean id="filterSecurityInterceptor" class="com.hand.security.utils.MyFilterSecurityInterceptor"> <b:property name="rejectPublicInvocations" value="true"/> <!-- 用戶擁有的權限 --> <b:property name="accessDecisionManager" ref="accessDecisionManager" /> <!-- 用戶是否擁有所請求資源的權限 --> <b:property name="authenticationManager" ref="authenticationManager" /> <!-- 資源與權限對應關係 --> <b:property name="securityMetadataSource" ref="securityMetadataSource" /> </b:bean> <!-- 二、更改驗證信息加載方式 --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="mUserDetailsService"> <!--若是用戶的密碼採用加密的話 <password-encoder hash="md5" /> --> </authentication-provider> </authentication-manager> <!-- 一、配置自定義類MUserDetailsService --> <b:bean id="mUserDetailsService" class="com.hand.security.service.impl.MUserDetailsService" /> <!--訪問決策器,決定某個用戶具備的角色,是否有足夠的權限去訪問某個資源 --> <b:bean id="accessDecisionManager" class="com.hand.security.utils.MyAccessDecisionManager"></b:bean> <!--資源源數據定義,將全部的資源和權限對應關係創建起來,即定義某一資源能夠被哪些角色訪問 --> <b:bean id="securityMetadataSource" class="com.hand.security.utils.MFilterInvocationSecurityMetadataSource" ></b:bean> </b:beans>