本文将具体介绍在Spring Boot中如何使用Spring Security进行安全控制,权限控制数据均有数据库查询。

1.背景

Spring Security 主要是在访问前添加过滤器,过滤器中主要起作用的为  访问鉴权authenticationManager(有没有权限访问系统)   和  访问决策器accessDecisionManager(可以访问系统的哪些资源,当时此处涉及查询数据库资源,还需要数据资源查询securityMetadataSource),具体的对应springmvc 中的配置地址为:http://blog.51cto.com/5148737/1615882,本文将基于上文的改造,改造成springboot版本。

2.添加maven依赖

<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>

到此为止,项目路径如下

~S_H`L@(Q)XZ1$_E90ZFU46.png

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,如下

R%%0QM1PMDCF[HU]8D@`W0Z.png

但是我们要讲的是,自定义权限控制,ok,下面继续


3.构建securiy配置文件WebSecurityConfig,需要继承WebSecurityConfigurerAdapter

   在这个类中,加入了自定义访问鉴权,访问决策器,资源池查询 


@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();
    }


}

相关说明:

  1. 该类需要继承WebSecurityConfigurerAdapter,重写configure方法,注解

    @Configuration:为配置文件,自动加载;

    @EnableWebSecurity:表示开启security权限控制

  2. 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的方式。


  3. 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;
    }
}