【權限管理】springboot集成security


摘自:

  https://www.cnblogs.com/hhhshct/p/9726378.html

  https://blog.csdn.net/weixin_42849689/article/details/89957823

  https://blog.csdn.net/zhaoxichen_10/article/details/88713799

  http://www.imooc.com/article/287214

一、Spring Security簡介

  Spring Security是一個能夠為基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重復代碼的工作。它是一個輕量級的安全框架,它確保基於Spring的應用程序提供身份驗證和授權支持。它與Spring MVC有很好地集成,並配備了流行的安全算法實現捆綁在一起。安全主要包括兩個操作“認證”與“驗證”(有時候也會叫做權限控制)。“認證”是為用戶建立一個其聲明的角色的過程,這個角色可以一個用戶、一個設備或者一個系統。“驗證”指的是一個用戶在你的應用中能夠執行某個操作。在到達授權判斷之前,角色已經在身份認證過程中建立了。

  用戶登陸,會被AuthenticationProcessingFilter攔截,調用AuthenticationManager的實現,而且AuthenticationManager會調用ProviderManager來獲取用戶驗證信息(不同的Provider調用的服務不同,因為這些信息可以是在數據庫上,可以是在LDAP服務器上,可以是xml配置文件上等),如果驗證通過后會將用戶的權限信息封裝一個User放到spring的全局緩存SecurityContextHolder中,以備后面訪問資源時使用。
  訪問資源(即授權管理),訪問url時,會通過AbstractSecurityInterceptor攔截器攔截,其中會調用FilterInvocationSecurityMetadataSource的方法來獲取被攔截url所需的全部權限,在調用授權管理器AccessDecisionManager,這個授權管理器會通過spring的全局緩存SecurityContextHolder獲取用戶的權限信息,還會獲取被攔截的url和被攔截url所需的全部權限,然后根據所配的策略(有:一票決定,一票否定,少數服從多數等),如果權限足夠,則返回,權限不夠則報錯並調用權限不足頁面。

二、Spring Security的執行過程

 

 

三、Spring Security代碼實現

Spring Security的核心配置類是 WebSecurityConfig,抽象類
這是權限管理啟動的入口,這里我們自定義一個實現類去它。然后編寫我們需要處理的控制邏輯。
下面是代碼,里面寫的注釋也比較詳細。在里面還依賴了幾個自定義的類,都是必須配置的。分別是
userService,
myFilterInvocationSecurityMetadataSource,
myAccessDecisionManager,

authenticationAccessDeniedHandler

3.1 WebSecurityConfig

package com.example.demo.config;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import com.example.demo.service.UserService;
/**
 * spring-security權限管理的核心配置
 * @author wjqhuaxia
 *
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) //全局
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;//實現了UserDetailsService接口
    @Autowired
    private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;//權限過濾器(當前url所需要的訪問權限)
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;//權限決策器
    @Autowired
    private AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;//自定義錯誤(403)返回數據

    /**
     * 自定義的加密算法
     * @return
     */
    @Bean
    public PasswordEncoder myPasswordEncoder() {
    	return new MyPasswordEncoder(); 
    }
    /**
     *  配置userDetails的數據源,密碼加密格式
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(myPasswordEncoder());
    }
    /**
     * 配置放行的資源
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
           .antMatchers("/index.html", "/static/**","/loginPage","/register")
           // 給 swagger 放行;不需要權限能訪問的資源
           .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui", "/configuration/security");
    }
    
    /**
     * 這段配置,我認為就是配置Security的認證策略, 每個模塊配置使用and結尾。
		authorizeRequests()配置路徑攔截,表明路徑訪問所對應的權限,角色,認證信息。
		formLogin()對應表單認證相關的配置
		logout()對應了注銷相關的配置
		httpBasic()可以配置basic登錄
     */
    /**
     * HttpSecurity包含了原數據(主要是url)
     * 1.通過withObjectPostProcessor將MyFilterInvocationSecurityMetadataSource和MyAccessDecisionManager注入進來
     * 2.此url先被MyFilterInvocationSecurityMetadataSource處理,然后 丟給 MyAccessDecisionManager處理
     * 3.如果不匹配,返回 MyAccessDeniedHandler
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    		// authorizeRequests()配置路徑攔截,表明路徑訪問所對應的權限,角色,認證信息
        	http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                        o.setAccessDecisionManager(myAccessDecisionManager);
                        return o;
                    }
                })
                .and()
            // formLogin()對應表單認證相關的配置
            .formLogin()
            	.loginPage("/loginPage")
            	.loginProcessingUrl("/login")
            	.usernameParameter("username")
            	.passwordParameter("password")
            	.permitAll()
            .failureHandler(new AuthenticationFailureHandler() {
	            @Override
	            public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
	                httpServletResponse.setContentType("application/json;charset=utf-8");
	                PrintWriter out = httpServletResponse.getWriter();
	                StringBuffer sb = new StringBuffer();
	                sb.append("{\"status\":\"error\",\"msg\":\"");
	                if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
	                    sb.append("用戶名或密碼輸入錯誤,登錄失敗!");
	                } else {
	                    sb.append("登錄失敗!");
	                }
	                sb.append("\"}");
	                out.write(sb.toString());
	                out.flush();
	                out.close();
	            }
            }).successHandler(new AuthenticationSuccessHandler() {
            @Override
	            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
	                httpServletResponse.setContentType("application/json;charset=utf-8");
	                PrintWriter out = httpServletResponse.getWriter();
	                String s = "{\"status\":\"success\",\"msg\":\"登陸成功\"}";
	                out.write(s);
	                out.flush();
	                out.close();
	            }
            }).and()
            // logout()對應了注銷相關的配置
            .logout()
            	.permitAll()
            	.and()
            	.csrf()
            	.disable()
        	.exceptionHandling()
        		.accessDeniedHandler(authenticationAccessDeniedHandler);
    }
}

3.2 UserService

UserServiceImpl實現了UserDetailsService接口中的loadUserByUsername方法,方法執行成功后返回UserDetails對象,為構建Authentication對象提供必須的信息。UserDetails中包含了用戶名,密碼,角色等信息。

package com.example.demo.service.impl;

import java.util.ArrayList;
import java.util.List;

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.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.dao.PermissionMapper;
import com.example.demo.dao.RoleMapper;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.Permission;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
/**
 * 實現了UserDetailsService接口中的loadUserByUsername方法
 * 執行登錄,構建Authentication對象必須的信息,
 * 如果用戶不存在,則拋出UsernameNotFoundException異常
 * @author wjqhuaxia
 *
 */
@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private PermissionMapper permissionMapper;
	@Autowired
	private RoleMapper roleMapper;
	@Autowired
	private UserMapper userMapper;
	@Autowired
	private PasswordEncoder passwordEncoder;
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userMapper.selectByUsername(username);
		if (user != null) {
            List<Permission> permissions = permissionMapper.findByUserId(user.getId());
            List<GrantedAuthority> grantedAuthorities = new ArrayList <>();
            for (Permission permission : permissions) {
                if (permission != null && permission.getPermissionname()!=null) {

                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getPermissionname());
                grantedAuthorities.add(grantedAuthority);
                }
            }
            return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
        } else {
            throw new UsernameNotFoundException("username: " + username + " do not exist!");
        } 
	}

	@Transactional
	@Override
	public void userRegister(String username, String password) {
		User user  = new User();
		user.setUsername(passwordEncoder.encode(username));
		user.setPassword(password);
		userMapper.insert(user);
		User rtnUser =userMapper.selectByUsername(username);
		//注冊成功默認給用戶的角色是user
		roleMapper.insertUserRole(rtnUser.getId(), 2);
	}

}

3.3 MyFilterInvocationSecurityMetadataSource

自定義權限過濾器,繼承了 SecurityMetadataSource(權限資源接口),過濾所有請求,核查這個請求需要的訪問權限;主要實現Collection<ConfigAttribute> getAttributes(Object o)方法,此方法中可編寫用戶邏輯,根據用戶預先設定的用戶權限列表,返回訪問此url需要的權限列表。

package com.example.demo.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 org.springframework.util.StringUtils;

import com.example.demo.dao.PermissionMapper;
import com.example.demo.model.Permission;
/**
 * 自定義權限過濾器
 * FilterInvocationSecurityMetadataSource(權限資源過濾器接口)繼承了 SecurityMetadataSource(權限資源接口)
 * Spring Security是通過SecurityMetadataSource來加載訪問時所需要的具體權限;Metadata是元數據的意思。
 * 自定義權限資源過濾器,實現動態的權限驗證
 * 它的主要責任就是當訪問一個url時,返回這個url所需要的訪問權限
 * @author wjqhuaxia
 *
 */
@Service
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

	private static final Logger log = LoggerFactory.getLogger(MyFilterInvocationSecurityMetadataSource.class);
	
	@Autowired
	private PermissionMapper permissionMapper;

	private HashMap<String, Collection<ConfigAttribute>> map = null;

	/**
	 * 加載權限表中所有權限
	 */
	public void loadResourceDefine() {
		map = new HashMap<String, Collection<ConfigAttribute>>();

		List<Permission> permissions = permissionMapper.findAll();
		for (Permission permission : permissions) {
			if(StringUtils.isEmpty(permission.getPermissionname())){
				continue;
			}
			if(StringUtils.isEmpty(permission.getUrl())){
				continue;
			}
			ConfigAttribute cfg = new SecurityConfig(permission.getPermissionname());
			List<ConfigAttribute> list = new ArrayList<>();
			list.add(cfg);
			// TODO:如果一個url對應多個權限,這里有問題
			map.put(permission.getUrl(), list);
		}

	}

	/**
	 * 此方法是為了判定用戶請求的url 是否在權限表中,如果在權限表中,則返回給 decide 方法, 用來判定用戶
	 * 是否有此權限。如果不在權限表中則放行。
	 */
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
		if (map == null) {
			loadResourceDefine();
		}
		// object 中包含用戶請求的request的信息
		HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
		for (Entry<String, Collection<ConfigAttribute>> entry : map.entrySet()) {
			String url = entry.getKey();
			if (new AntPathRequestMatcher(url).matches(request)) {
				return map.get(url);
			}
		}
		/**
         * @Author: Galen
         * @Description: 如果本方法返回null的話,意味着當前這個請求不需要任何角色就能訪問
         * 此處做邏輯控制,如果沒有匹配上的,返回一個默認具體權限,防止漏缺資源配置
         **/
        log.info("當前訪問路徑是{},這個url所需要的訪問權限是{}", request.getRequestURL(), "ROLE_LOGIN");
        return SecurityConfig.createList("ROLE_LOGIN");
	}
	/**
	 * 此處方法如果做了實現,返回了定義的權限資源列表,
     * Spring Security會在啟動時校驗每個ConfigAttribute是否配置正確,
     * 如果不需要校驗,這里實現方法,方法體直接返回null即可
	 */
	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		return null;
	}
	/**
	 * 方法返回類對象是否支持校驗,
     * web項目一般使用FilterInvocation來判斷,或者直接返回true
	 */
	@Override
	public boolean supports(Class<?> clazz) {
		return true;
	}
	
}

3.4 AuthenticationAccessDeniedHandler

自定義權限決策管理器,需要實現AccessDecisionManager 的 void decide(Authentication auth, Object object, Collection<ConfigAttribute> cas) 方法,在上面的過濾器中,我們已經得到了訪問此url需要的權限;那么,decide方法,先查詢此用戶當前擁有的權限,然后與上面過濾器核查出來的權限列表作對比,以此判斷此用戶是否具有這個訪問權限,決定去留!所以顧名思義為權限決策器。

package com.example.demo.config;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 拒簽(403響應)處理器
 * Denied是拒簽的意思
 * @author wjqhuaxia
 *
 */
@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        resp.setContentType("application/json;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.write("{\"status\":\"error\",\"msg\":\"權限不足,請聯系管理員!\"}");
        out.flush();
        out.close();
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM