本文将具体介绍在Spring Boot中如何使用Spring Security进行安全控制,权限控制数据均有数据库查询。
1.背景
Spring Security 主要是在访问前添加过滤器,过滤器中主要起作用的为 访问鉴权authenticationManager (有没有权限访问系统) 和 访问决策器accessDecisionManager (可以访问系统的哪些资源,当时此处涉及查询数据库资源,还需要数据资源查询securityMetadataSource),具体的对应springmvc 中的配置地址为: http://blog.51cto.com/5148737/1615882,本文将基于上文的改造,改造成springboot版本。
<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>
到此为止,项目路径如下
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.java
@Controller public class HomeController { @RequestMapping("/") public void index(HttpServletRequest request,HttpServletResponse response) throws IOException { response.getWriter().write("index"); } }
这个时候如果项目跑起来的话,springsecurity会使用内部默认控制,控制台会打印密码,用户名为user,如下
但是我们要讲的是,自定义权限控制,ok,下面继续
@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 :密码添加盐值
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; } }
@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; } }