本文將具體介紹在Spring Boot中如何使用Spring Security進行安全控制,權限控制數據均有數據庫查詢。html
1.背景java
Spring Security 主要是在訪問前添加過濾器,過濾器中主要起做用的爲 訪問鑑權authenticationManager(有沒有權限訪問系統) 和 訪問決策器accessDecisionManager(能夠訪問系統的哪些資源,當時此處涉及查詢數據庫資源,還須要數據資源查詢securityMetadataSource),具體的對應springmvc 中的配置地址爲:https://blog.51cto.com/5148737/1615882,本文將基於上文的改造,改形成springboot版本。web
2.添加maven依賴spring
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 若是須要jsp支持security標籤,須要添加這個 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> </dependency>
到此爲止,項目路徑以下sql
pom.xml數據庫
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.gosun</groupId> <artifactId>cdn</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> </project>
HomeController.javaapache
@Controller public class HomeController { @RequestMapping("/") public void index(HttpServletRequest request,HttpServletResponse response) throws IOException { response.getWriter().write("index"); } }
這個時候若是項目跑起來的話,springsecurity會使用內部默認控制,控制檯會打印密碼,用戶名爲user,以下安全
可是咱們要講的是,自定義權限控制,ok,下面繼續springboot
3.構建securiy配置文件WebSecurityConfig,須要繼承WebSecurityConfigurerAdaptersession
在這個類中,加入了自定義訪問鑑權,訪問決策器,資源池查詢
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailService userDetailService; @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setPasswordEncoder(passwordEncoder()); authProvider.setUserDetailsService(userDetailService); ReflectionSaltSource saltSource = new ReflectionSaltSource(); saltSource.setUserPropertyToUse("username"); authProvider.setSaltSource(saltSource); auth.authenticationProvider(authProvider); } @Bean public Md5PasswordEncoder passwordEncoder() { return new Md5PasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.authorizeRequests() .antMatchers("/assets/**", "/portal/**", "/login","/redirect","/login/**").permitAll() .antMatchers("/schedual/area_isp_ip", "/403", "/404.jsp", "/logout", "/favicon.ico","/favicon.html").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .successHandler(authenticationSuccessHandler).defaultSuccessUrl("/").permitAll() .failureUrl("/login?error").permitAll() .and() .logout() .permitAll() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { public <O extends FilterSecurityInterceptor> O postProcess( O fsi) { fsi.setSecurityMetadataSource(mySecurityMetadataSource()); fsi.setAccessDecisionManager(myAccessDecisionManager()); return fsi; } }); } @Bean public FilterInvocationSecurityMetadataSource mySecurityMetadataSource() { MyInvocationSecurityMetadataSourceService securityMetadataSource = new MyInvocationSecurityMetadataSourceService(); return securityMetadataSource; } @Bean public AccessDecisionManager myAccessDecisionManager() { return new MyAccessDecisionManager(); } }
相關說明:
該類須要繼承WebSecurityConfigurerAdapter,重寫configure方法,註解
@Configuration:爲配置文件,自動加載;
@EnableWebSecurity:表示開啓security權限控制
configure(HttpSecurity http)
http.csrf().disable() 禁用了csrf,支持jsp時須要加此條件
http.authorizeRequests().xxxxx 開始添加相關條件
antMatchers.....爲須要過濾的url,即這些地址url都不須要進行權校驗
.formLogin().loginPage("/login") 定義了默認的登錄地址
.successHandler.....定義了登錄成功後的處理方法,主要將用戶信息放入session等
.failureUrl.....定義了失敗的url
.withObjectPostProcessor......則定義了訪問決策器、資源池查詢 兩個方法
springmvc中定義filter,將訪問決策器、訪問決策器、資源池查詢定義在filter中,在網上一些案例中,看到其餘文章springboot 整合springsecurity的時候,採用添加filter的方式,可是親自試驗後發現有問題,antMatchers會失效,若是全部的url都須要進行權限校驗,那麼能夠採用添加filter的形式,可是若是向上述,須要部分url好比說靜態文件直接能夠訪問,那麼必須採用這種withObjectPostProcessor的方式。
configure(AuthenticationManagerBuilder auth) 定義了訪問鑑權
其中鑑權最重要的是須要 authProvider,
setPasswordEncoder :定義何種密碼校驗方式Md5PasswordEncoder
setUserDetailsService :自定義了數據庫查詢用戶
setSaltSource :密碼添加鹽值
4.資源池查詢類(MyInvocationSecurityMetadataSourceService)
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource { private AntPathMatcher urlMatcher = new AntPathMatcher(); private static Map <String,Collection <ConfigAttribute>> resourceMap = null; @Autowired CommonDao commonDao; /** * 加載URL權限配置 */ private void loadResourceDefine() { long starttime = new Date().getTime(); String sql = "SELECT * FROM auth_resource"; resourceMap = new HashMap<String,Collection <ConfigAttribute >> (); List<Properties> resourceList = commonDao.queryForList(sql); List<Map> roleList = commonDao.queryForList("SELECT * FROM auth_role"); Map<String,String> roleIdMap = new HashMap(); for(Map roleMap:roleList){ String roleId = roleMap.get("id").toString(); String rolename = roleMap.get("rolename").toString(); roleIdMap.put(roleId, rolename); } long endtime = new Date().getTime(); System.out.println("查詢完畢,耗時"+(endtime-starttime)+"ms"); for(Map dataMap:resourceList){ String urlPattern =""; if(dataMap.get("url_pattern")!=null){ urlPattern = dataMap.get("url_pattern").toString(); } String[] roleIds = new String[0]; if(dataMap.get("access_role")!=null&&!dataMap.get("access_role").equals("")){ String acce***ole = dataMap.get("access_role").toString(); roleIds = acce***ole.split(","); } Collection <ConfigAttribute> atts = new ArrayList < ConfigAttribute >(); for(String roleId:roleIds){ ConfigAttribute ca = new SecurityConfig(roleIdMap.get(roleId)); atts.add(ca); } resourceMap.put(urlPattern,atts); } endtime = new Date().getTime(); System.out.println("加載系統權限配置完畢,耗時"+(endtime-starttime)+"ms"); } public boolean isResourceMapEmpty(){ if(resourceMap==null){ return true; }else{ return false; } } public Collection<ConfigAttribute> getAllConfigAttributes() { // TODO Auto-generated method stub return null; } public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { if(resourceMap==null) { loadResourceDefine(); } // TODO Auto-generated method stub String url =((FilterInvocation)object).getRequestUrl(); if(resourceMap != null){ Set<String> urlPatternSet = resourceMap.keySet(); for(String urlPattern:urlPatternSet){ if(urlMatcher.match(urlPattern, url)){ return resourceMap.get(urlPattern); } } } return null; } public boolean supports(Class<?> arg0) { // TODO Auto-generated method stub return true; } public String resourceSelfMatcher(String resURL){ return null; } /** * 刷新資源配置 */ public void refreshResource(){ loadResourceDefine(); } }
5.訪問決策器(MyAccessDecisionManager)
public class MyAccessDecisionManager implements AccessDecisionManager { @Autowired MyInvocationSecurityMetadataSourceService securityMetadataSource; public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,InsufficientAuthenticationException { // TODO Auto-generated method stub // if(securityMetadataSource.isResourceMapEmpty()){ // securityMetadataSource.refreshResource(); // } if (configAttributes == null ) throw new AccessDeniedException("對不起,您沒有此權限"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(new Date())+":\t"+object.toString()); for(ConfigAttribute ca:configAttributes){ String needRole = ca.getAttribute(); for(GrantedAuthority userGA:authentication.getAuthorities()) { if(needRole.equals(userGA.getAuthority())) { // ga is user's role. return ; } } } throw new AccessDeniedException("對不起,您沒有此權限"); } public boolean supports(ConfigAttribute arg0) { // TODO Auto-generated method stub return true; } public boolean supports(Class<?> arg0) { // TODO Auto-generated method stub return true; } }
6.自定義用戶查詢(MyUserDetailService)
@Service("myUserDetailService") public class MyUserDetailService implements UserDetailsService { @Autowired CommonDao commonDao; String userTable = "auth_user"; String roleTable = "auth_role"; String menuTable = "auth_resource"; public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException, DataAccessException { Map userMap= commonDao.queryForOne("SELECT * FROM `"+userTable+"` WHERE username='"+username+"'"); if(userMap!=null&userMap.containsKey("username")){ //初始化角色信息 Collection <GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); String[] userRoles = userMap.get("user_role").toString().split(","); for(String userRole:userRoles){ Map roleMap = commonDao.queryForOne("SELECT * FROM `"+roleTable+"` WHERE id="+ userRole); if(roleMap!=null && roleMap.containsKey("rolename")){ SimpleGrantedAuthority authority = new SimpleGrantedAuthority(roleMap.get("rolename").toString()); authorities.add(authority); } } boolean enabled = false; String nickname = username; String telephone = ""; String email = ""; int sex = 0; String password = ""; if(userMap.get("telephone")!=null) telephone = userMap.get("telephone").toString(); if(userMap.get("password")!=null) password = userMap.get("password").toString(); if(userMap.get("nickname")!=null) nickname = userMap.get("nickname").toString(); if(userMap.get("email")!=null) email = userMap.get("email").toString(); if(userMap.get("sex")!=null) sex = Integer.parseInt(userMap.get("sex").toString()); if(Integer.parseInt(userMap.get("enabled").toString())==1) enabled = true; User userdtails = new User(username, password, enabled, true, true,true, authorities,null,nickname,telephone,email,sex); return userdtails; }else{ return null; } } }
7.權限校驗成功返回handler (AuthenticationSuccessHandler)
@Service public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Autowired private CommonDao commonDao; private RequestCache requestCache; public AuthenticationSuccessHandler(){ this.requestCache = new HttpSessionRequestCache(); } @Override public void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication)throws ServletException, IOException { User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); request.getSession().setAttribute("userDetails", userDetails); super.onAuthenticationSuccess(request, response, authentication); return; } }