使用spring Security3的四種方法概述css
那麼在Spring Security3的使用中,有4種方法:html
一種是所有利用配置文件,將用戶、權限、資源(url)硬編碼在xml文件中,已經實現過,並通過驗證;java
二種是用戶和權限用數據庫存儲,而資源(url)和權限的對應採用硬編碼配置,目前這種方式已經實現,並通過驗證。git
三種是細分角色和權限,並將用戶、角色、權限和資源均採用數據庫存儲,而且自定義過濾器,代替原有的FilterSecurityInterceptor過濾器,
並分別實現AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,並在配置文件中進行相應配置。
目前這種方式已經實現,並通過驗證。github
http://blog.csdn.net/woshisap/article/details/7250428web
添加 Spring Security 配置類
添加spring security到咱們應用中第一步是要建立Spring Security Java 配置類。
這個配置建立一個叫springSecurityFilterChain的Servlet過濾器,來對咱們應用中全部的安全相關的事項(保護應用的全部url,驗證用戶名密碼,表單重定向等)負責。spring
import org.springframework.beans.factory.annotation.Autowired; 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.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired CustomSuccessHandler customSuccessHandler; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("USER"); auth.inMemoryAuthentication().withUser("admin").password("root123").roles("ADMIN"); auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/home").access("hasRole('USER')") .antMatchers("/admin/**").access("hasRole('ADMIN')") .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") .and().formLogin().loginPage("/login").successHandler(customSuccessHandler) .usernameParameter("ssoId").passwordParameter("password") .and().csrf() .and().exceptionHandling().accessDeniedPage("/Access_Denied"); } }
http://www.2cto.com/kf/201605/506019.html數據庫
序:
本文使用springboot+mybatis+SpringSecurity 實現數據庫動態的管理用戶、角色、權限管理bootstrap
本文細分角色和權限,並將用戶、角色、權限和資源均採用數據庫存儲,而且自定義濾器,代替原有的FilterSecurityInterceptor過濾器,
並分別實現AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,並在配置文件中進行相應配置。緩存
使用衆多的攔截器對url攔截,以此來管理權限。可是這麼多攔截器,筆者不可能對其一一來說,主要講裏面核心流程的兩個。
首先,權限管理離不開登錄驗證的,因此登錄驗證攔截器AuthenticationProcessingFilter要講;
還有就是對訪問的資源管理吧,因此資源管理攔截器AbstractSecurityInterceptor要講;
但攔截器裏面的實現須要一些組件來實現,因此就有了AuthenticationManager、accessDecisionManager等組件來支撐。
如今先大概過一遍整個流程,
用戶登錄,會被AuthenticationProcessingFilter攔截(即認證管理),調用AuthenticationManager的實現,並且AuthenticationManager會調用ProviderManager來獲取用戶驗證信息(不一樣的Provider調用的服務不一樣,由於這些信息能夠是在數據庫上,能夠是在LDAP服務器上,能夠是xml配置文件上等),
若是驗證經過後會將用戶的權限信息封裝一個User放到spring的全局緩存SecurityContextHolder中,以備後面訪問資源時使用。
訪問資源(即受權管理),訪問url時,會經過AbstractSecurityInterceptor攔截器攔截,
其中會調用FilterInvocationSecurityMetadataSource的方法來獲取被攔截url所需的所有權限,
在調用受權管理器AccessDecisionManager,這個受權管理器會經過spring的全局緩存SecurityContextHolder獲取用戶的權限信息,還會獲取被攔截的url和被攔截url所需的所有權限,而後根據所配的策略(有:一票決定,一票否認,少數服從多數等),若是權限足夠,則返回,權限不夠則報錯並調用權限不足頁面。
http://blog.csdn.net/u013412066/article/details/50667960
本文設計和代碼是基於 上一篇博客(請點擊)
springboot+mybatis+SpringSecurity 實現用戶角色數據庫管理
進行修改。
本文目錄:
1:數據庫表設計
2:權限表的業務
3:springSecurity 配置修改
4:修改home.html 文件
5:修改HomeController.Java 文件
6:測試檢驗
目錄結構以下:
因爲本文增長了權限表因此本文的數據庫表爲5個分別是: 用戶表、角色表、權限表、用戶角色中間表、角色權限中間表
初始化數據
注意:Sys_permission 表的url通配符爲兩顆星,好比說 /user下的全部url,應該寫成 /user/**; 權限的名字能夠隨意起名
DROP TABLE IF EXISTS `Sys_User`; CREATE TABLE `Sys_User`( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `username` VARCHAR(200) NOT NULL, `password` VARCHAR(200) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `Sys_Role`; CREATE TABLE `Sys_Role`( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(200) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `Sys_permission`; CREATE TABLE `Sys_permission`( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(200) NOT NULL, `description` VARCHAR(200) DEFAULT NULL, `url` VARCHAR(200) NOT NULL, `pid` BIGINT DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `Sys_role_user`; CREATE TABLE `Sys_role_user`( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `sys_user_id` BIGINT UNSIGNED NOT NULL, `sys_role_id` BIGINT UNSIGNED NOT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; DROP TABLE IF EXISTS `Sys_permission_role`; CREATE TABLE `Sys_permission_role`( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `role_id` BIGINT UNSIGNED NOT NULL, `permission_id` BIGINT UNSIGNED NOT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; insert into SYS_USER (id,username, password) values (1,'admin', 'admin'); insert into SYS_USER (id,username, password) values (2,'abel', 'abel'); insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN'); insert into SYS_ROLE(id,name) values(2,'ROLE_USER'); insert into SYS_ROLE_USER(SYS_USER_ID,sys_role_id) values(1,1); insert into SYS_ROLE_USER(SYS_USER_ID,sys_role_id) values(2,2); INSERT INTO `Sys_permission` VALUES ('1', 'ROLE_HOME', 'home', '/', null), ('2', 'ROLE_ADMIN', 'ABel', '/admin', null); INSERT INTO `Sys_permission_role` VALUES ('1', '1', '1'), ('2', '1', '2'), ('3', '2', '1');
package com.us.example.domain; /** * Created by yangyibo on 17/1/20. */ public class Permission { private int id; //權限名稱 private String name; //權限描述 private String descritpion; //受權連接 private String url; //父節點id private int pid; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescritpion() { return descritpion; } public void setDescritpion(String descritpion) { this.descritpion = descritpion; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getPid() { return pid; } public void setPid(int pid) { this.pid = pid; } }
在 com.us.example.dao 包下新建PermissionDao.java 文件。
package com.us.example.dao; import com.us.example.config.MyBatisRepository; import com.us.example.domain.Permission; import java.util.List; /** * Created by yangyibo on 17/1/20. */ public interface PermissionDao { public List<Permission> findAll(); public List<Permission> findByAdminUserId(int userId); }
在src/resource/mapper目錄下新建對應的mapper.xml 文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.us.example.dao.PermissionDao"> <select id="findAll" resultType="com.us.example.domain.Permission"> SELECT * from Sys_permission ; </select> <select id="findByAdminUserId" parameterType="int" resultType="com.us.example.domain.Permission"> select p.* from Sys_User u LEFT JOIN sys_role_user sru on u.id= sru.Sys_User_id LEFT JOIN Sys_Role r on sru.Sys_Role_id=r.id LEFT JOIN Sys_permission_role spr on spr.role_id=r.id LEFT JOIN Sys_permission p on p.id =spr.permission_id where u.id=#{userId} </select> </mapper>
修改com.us.example.config包下的 WebSecurityConfig.java 文件以下:
package com.us.example.config; import com.us.example.service.CustomUserService; import com.us.example.service.MyFilterSecurityInterceptor; 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.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; /** * Created by yangyibo on 17/1/18. */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyFilterSecurityInterceptor myFilterSecurityInterceptor; @Bean UserDetailsService customUserService(){ //註冊UserDetailsService 的bean return new CustomUserService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserService()); //user Details Service驗證 } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() //任何請求,登陸後能夠訪問 .and() .formLogin() .loginPage("/login") .failureUrl("/login?error") .permitAll() //登陸頁面用戶任意訪問 .and() .logout().permitAll(); //註銷行爲任意訪問 http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class); } }
修改CustomUserService.java 內容以下:
package com.us.example.service; import com.us.example.dao.PermissionDao; import com.us.example.dao.UserDao; import com.us.example.domain.Permission; import com.us.example.domain.SysRole; import com.us.example.domain.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.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * Created by yangyibo on 17/1/18. */ @Service public class CustomUserService implements UserDetailsService { //自定義UserDetailsService 接口 @Autowired UserDao userDao; @Autowired PermissionDao permissionDao; public UserDetails loadUserByUsername(String username) { SysUser user = userDao.findByUserName(username); if (user != null) { List<Permission> permissions = permissionDao.findByAdminUserId(user.getId()); List<GrantedAuthority> grantedAuthorities = new ArrayList <>(); for (Permission 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!"); } } }
在com.us.example.service 包下新增
MyAccessDecisionManager.java 文件
package com.us.example.service; 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; /** * Created by yangyibo on 17/1/19. */ @Service public class MyAccessDecisionManager implements AccessDecisionManager { // 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; } } } throw new AccessDeniedException("no right"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
在com.us.example.service 包下新增
MyFilterSecurityInterceptor.java 文件
package com.us.example.service; 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.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Service; import java.io.IOException; /** * Created by yangyibo on 17/1/19. */ @Service public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { @Autowired private FilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) { super.setAccessDecisionManager(myAccessDecisionManager); } @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.securityMetadataSource; } }
在com.us.example.service 包下新增MyInvocationSecurityMetadataSourceService.java文件
package com.us.example.service; import com.us.example.dao.PermissionDao; import com.us.example.domain.Permission; 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.*; /** * Created by yangyibo on 17/1/19. */ @Service public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource { @Autowired private PermissionDao permissionDao; private HashMap<String, Collection<ConfigAttribute>> map =null; /** * 加載權限表中全部權限 */ public void loadResourceDefine(){ map = new HashMap<>(); Collection<ConfigAttribute> array; ConfigAttribute cfg; List<Permission> permissions = permissionDao.findAll(); for(Permission 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; } }
修改src/resources/templates目錄下 的home.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> <head> <meta content="text/html;charset=UTF-8"/> <title sec:authentication="name"></title> <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" /> <style type="text/css"> body { padding-top: 50px; } .starter-template { padding: 40px 15px; text-align: center; } </style> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Spring Security演示</a> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><a th:href="@{/}"> 首頁 </a></li> <li><a th:href="@{/admin}"> admin </a></li> </ul> </div><!--/.nav-collapse --> </div> </nav> <div class="container"> <div class="starter-template"> <h1 th:text="${msg.title}"></h1> <p class="bg-primary" th:text="${msg.content}"></p> <div sec:authorize="hasRole('ROLE_HOME')"> <!-- 用戶類型爲ROLE_ADMIN 顯示 --> <p class="bg-info" th:text="${msg.etraInfo}"></p> </div> <div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 用戶類型爲ROLE_ADMIN 顯示 --> <p class="bg-info">恭喜您,您有 ROLE_ADMIN 權限 </p> </div> <form th:action="@{/logout}" method="post"> <input type="submit" class="btn btn-primary" value="註銷"/> </form> </div> </div> </body> </html>
package com.us.example.controller; import com.us.example.domain.Msg; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * Created by yangyibo on 17/1/18. */ @Controller public class HomeController { @RequestMapping("/") public String index(Model model){ Msg msg = new Msg("測試標題","測試內容","歡迎來到HOME頁面,您擁有 ROLE_HOME 權限"); model.addAttribute("msg", msg); return "home"; } @RequestMapping("/admin") @ResponseBody public String hello(){ return "hello admin"; } }
啓動訪問 http://localhost:8080/ 到登陸頁面
因爲數據庫的配置 admin 用戶擁有 訪問 home和admin 頁面的權限。 abel 用戶只有訪問 home 的權限
使用admin 登陸
點擊 admin 按鈕 會反回結果 「hello admin「
使用abel 用戶登陸 點擊 點擊 admin 按鈕 頁面會報403
源碼地址:https://github.com/527515025/springBoot
參考資料:
http://www.tuicool.com/articles/jq6fuur#c-23220
http://blog.csdn.net/u012367513/article/details/38866465
http://blog.csdn.net/u012373815/article/details/54633046
https://github.com/527515025/springBoot
https://github.com/helloworldtang/springBoot