springboot 支持springsecurity 自定義數據庫查詢

本文將具體介紹在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;
    }
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章