[] Springboot integrated security rights management

Excerpt:

  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

A, Spring Security Profile

  Spring Security is able to provide a secure access control declarative security framework Spring-based solutions for enterprise applications. It provides a set can be configured in the Spring Bean application context, full use Spring IoC, DI (Inversion of Control Inversion of Control, DI: Dependency Injection dependency injection) and the AOP (Aspect Oriented Programming) function provides for the application declarative security access control functions, reducing the enterprise system security controls writing a lot of code duplication of work. It is a lightweight security framework that ensures authentication and authorization support for Spring-based applications. Spring MVC and it has well integrated and equipped with popular security algorithms bundled together. Security consists of two operations "certification" and "verification" (sometimes called the access control). "Certification" of its role is to establish a process for the declaration of the user, this role can be a user, a device or a system. "Verification" refers a user to perform an action in your application. Before reaching the authorized judge, the role has been established in the authentication process.

  User login, will be blocked AuthenticationProcessingFilter, call implementation AuthenticationManager, and AuthenticationManager calls ProviderManager to obtain user authentication information (different different service Provider call, because this information may be on the database, you can be on an LDAP server, it can be fine xml configuration file), if the verification use authority information of the user will be encapsulated into a user SecurityContextHolder spring in the global cache, ready for later access resources.
  Access to resources (ie, authorization management), when accessing url, will pass AbstractSecurityInterceptor interceptor to intercept, which calls FilterInvocationSecurityMetadataSource way to get all the required permissions intercepted url, calling the Authorization Manager AccessDecisionManager, Authorization Manager will pass this spring the global cache SecurityContextHolder obtain information about the user's permission, it will get intercepted and full access was blocked url url you want, and in accordance with the distribution policy (there: one vote decision, a negative vote, majority rule, etc.), If sufficient authority is returned, the access is not enough and calls the error are insufficient permissions page.

Second, the implementation process of Spring Security

 

 

Three, Spring Security code implementation

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

Guess you like

Origin www.cnblogs.com/wjqhuaxia/p/12078671.html