zoukankan      html  css  js  c++  java
  • 【权限管理】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();
        }
    }
    
  • 相关阅读:
    SQL Server创建复合索引时,复合索引列顺序对查询的性能影响
    SQL 查询性能优化----解决书签查找
    从源码分析 Spring 基于注解的事务
    jQuery最佳实践(不断更新中...)
    Java 8 LongAdders:管理并发计数器的正确方式
    Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock
    在IE8等不支持placeholder属性的浏览器中模拟placeholder效果
    颠覆式前端UI开发框架:React
    Whitecoin区块链钱包高级功能使用命令
    消息队列使用的四种场景介绍
  • 原文地址:https://www.cnblogs.com/wjqhuaxia/p/12078671.html
Copyright © 2011-2022 走看看