• Spring Boot Security配置用户认证和资源授权


    用户认证

    认证是指,用户是否在本系统,以及账号信息是否符合设定的预期。

    查询用户(UserDetailsService)

    实现UserDetailsService接口,根据用户名称,查询用户信息。可实现自定义查询用户,并设置用户的角色信息。

    实现了该接口,需要到配置对应的密码加密类已实现对用户密码的校验。因前端输入的密码是未加密,而数据库保存的密码是已加密的。

    @Service
    public class CustomerUserDetailsService implements UserDetailsService {
        //密码加密类
    	@Autowired
        private PasswordEncoder passwordEncoder;
        //加载用户信息
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            CustomerUserDetails userDetails = new CustomerUserDetails();
            userDetails.setUsername("user");
            userDetails.setPassword(passwordEncoder.encode("123123"));
            userDetails.setEnable(true);
            userDetails.setAuthorities(Collections.emptySet());
            userDetails.setAccountNonExpired(true);
            userDetails.setAccountNonLocked(true);
            userDetails.setCredentialsNonExpired(true);
            return userDetails;
        }
    
          /**
         * 根据用户名,返回用户角色
         */
        private Collection<? extends GrantedAuthority> loadRoleByUsername(String username){
            Collection<SimpleGrantedAuthority> collection = new HashSet<>();
            //测试数据
            if("admin".equals(username)) {
                collection.add(new SimpleGrantedAuthority("ADMIN"));
            }else {
                collection.add(new SimpleGrantedAuthority(username));
            }
            return collection;
        }
    
        /**
         * 角色信息可以参考SimpleGrantedAuthority类
         * 根据用户名, 返回用户组角色
         */
        private void loadGroupRoleByUsername(String username){
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    用户信息(UserDetails)

    只需要实现UserDetails接口即可,自定义参数不需要和接口调用类保持一致(set/get)但最好保持一致,该接口是UserDetailsService接口中返回参数。

    public class CustomerUserDetails implements UserDetails {
        //用户密码
        private String password;
        //用户名
        private String username;
        //用户角色信息
        private Collection<? extends GrantedAuthority> authorities;
        //启用
        private boolean enable;
        //认证未过期
        private boolean credentialsNonExpired;
        //账号未锁定
        private boolean accountNonExpired;
        //账号未锁定
        private boolean accountNonLocked;
        
       
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return this.authorities;
        }
    
        @Override
        public String getPassword() {
            return this.password;
        }
    
        @Override
        public String getUsername() {
            return this.username;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return this.accountNonExpired;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return this.accountNonLocked;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return this.credentialsNonExpired;
        }
    
        @Override
        public boolean isEnabled() {
            return this.enable;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
            this.authorities = authorities;
        }
    
        public boolean isEnable() {
            return enable;
        }
    
        public void setEnable(boolean enable) {
            this.enable = enable;
        }
    
        public void setCredentialsNonExpired(boolean credentialsNonExpired) {
            this.credentialsNonExpired = credentialsNonExpired;
        }
    
        public void setAccountNonExpired(boolean accountNonExpired) {
            this.accountNonExpired = accountNonExpired;
        }
    
        public void setAccountNonLocked(boolean accountNonLocked) {
            this.accountNonLocked = accountNonLocked;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    安全配置(WebSecurityConfigurerAdapter)

    因使用密码加密类,加密了密码,需要在配置中配置密码类。

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        //设置密码加密类
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    资源认证

    资源认证,一般是基于URL与角色的认证。
    需要自己定义

    安全数据元(FilterInvocationSecurityMetadataSource)

    通过数据库定义,URL和角色的授权关系。在将数据加入的缓存中已减少多次读取的问题。

    设计的时候:

    1. 如果只是需要登录即可访问的资源,可以设计一个角色,在用户注册时候就授予这个角色。
    2. 如果需要指定角色,则需要URL和角色一一对应。
    /**
     * 访问决策需要访问的资源
     */
    @Component
    public class CustomerFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
        private AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        private Map<String,Collection<ConfigAttribute>> urlAndRoles = new HashMap<>();
    
        /** 不校验即可请求 */
        private String[] permitAllUrl;
    
        /** 所有角色 */
        private List<SecurityConfig> roles = new ArrayList<>();
    
        /** 无角色Spring生成的角色名称*/
        private SecurityConfig  defaultRole = new SecurityConfig("ROLE_ANONYMOUS");
    
        public void defaultAttributes(String... attributes){
            this.permitAllUrl = attributes;
            refresh();
        }
    
        public void refresh(){
            for(String attribute : permitAllUrl){
                Collection<ConfigAttribute> collection = new HashSet<>();
                collection.add(defaultRole);
                collection.addAll(roles);
                urlAndRoles.put(attribute,collection);
            }
        }
        
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            String url = ((FilterInvocation) object).getRequestUrl();
            int index = url.indexOf("?");
            if(index != -1){
                url = url.substring(0,index);
            }
            Collection<ConfigAttribute> collection = getRoleByUrl(url);
            //如果为空,则不进行校验
            if(collection.isEmpty()){
    
            }
            return collection;
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            Set<ConfigAttribute> allAttributes = new HashSet<>();
            return allAttributes;
        }
    
        /**
         * 获取请求路径的角色
         *
         */
        public Collection<ConfigAttribute> getRoleByUrl(String url) {
            //收集 匹配到路径的角色
            Collection<ConfigAttribute> collection = new HashSet<>();
            //获取路径和角色的资源,一般放在缓存中
            Iterator<String> iterator = urlAndRoles.keySet().iterator();
            //这个角色是方便测试加入的,
            collection.add(new SecurityConfig("ADMIN"));
            while (iterator.hasNext()){
                String matchUrl = iterator.next();
                if(this.antPathMatcher.match(matchUrl,url)){
                    Collection<ConfigAttribute> matchCollection = urlAndRoles.get(matchUrl);
                    collection.addAll(matchCollection);
                }
            }
            return collection;
        }
        /**
         * 是否支持
         */
        @Override
        public boolean supports(Class<?> clazz) {
            return FilterInvocation.class.isAssignableFrom(clazz);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    访问决策管理

    在获取URL对应的角色时,会将用户的角色和获取的角色进行比较,查看用户是否有权限访问。

    /**
     * 访问决策管理
     * 讲通过URL获取到对应授权的角色,和当前用户拥有角色进行比较。  查看是否拥有请求当前资源权限
     */
    @Component
    public class CustomerAccessDecisionManager implements AccessDecisionManager {
        protected final Log logger = LogFactory.getLog(getClass());
        //一般进行角色校验
    
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            Iterator<ConfigAttribute> iterator = configAttributes.iterator();
            logger.info("decide");
            while (iterator.hasNext()){
                ConfigAttribute attribute = iterator.next();
                for(GrantedAuthority ga : authentication.getAuthorities()){
                    if(attribute.getAttribute().equals(ga.getAuthority())){
                        return;
                    }
                }
    
            }
            //返回访问拒绝信息
           throw new AccessDeniedException("Access reject!");
        }
    
        @Override
        public boolean supports(ConfigAttribute attribute) {
            return true;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    安全拦截器(FilterSecurityInterceptor)

    需要继承FilterSecurityInterceptor类,在这里设置安全元数据、访问决策管理。如果不需要的访问,可以通过重写进行覆盖父类的方法。

    @Component
    public class CustomerFilterSecurityInterceptor extends FilterSecurityInterceptor {
    
    	//初始化方法,加载访问决策管理,和安全元数据
    	/**
        @PostConstruct
        public void init(){
            logger.info("init info");
            super.setSecurityMetadataSource(new CustomerFilterInvocationSecurityMetadataSource());
            super.setAccessDecisionManager(new CustomerAccessDecisionManager());
        }
    */
    	@Autowired
    	@Override
    	//自动注入自定义安全元数据
    	public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource source){
       		super.setSecurityMetadataSource(source);
    	}
    	@Autowired
    	@Override
    	//自动注入自定义访问决策
    	public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager){
       		super.setAccessDecisionManager(accessDecisionManager);
    	}
        /**
         * 这一步可以不重写,使用系统自带的方法,如果不需要或是没有配置那些功能,则可以重写为自己想要的功能
         */
        @Override
        public void invoke(FilterInvocation fi) throws IOException, ServletException {
            logger.info("invoke");
            InterceptorStatusToken token = beforeInvocation(fi);
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }
            super.afterInvocation(token, null);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    安全配置(WebSecurityConfigurerAdapter)

    在用户认证,安全配置类中加入,安全拦截器功能

    /**
     * 配置类
     */
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        protected final Log logger = LogFactory.getLog(getClass());
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
        @Override
        public void configure(WebSecurity web) throws Exception {
            logger.info("configure(WebSecurity web)");
            //web.securityInterceptor(new CustomerFilterSecurityInterceptor());
             web.securityInterceptor(this.getApplicationContext().getBean(FilterSecurityInterceptor.class));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    第二种配置方法
    无需创建FilterSecurityInterceptor

    @Override
    public void init(WebSecurity web) throws Exception {
        HttpSecurity http = this.getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
            FilterSecurityInterceptor securityInterceptor = (FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
            AccessDecisionManager accessDecisionManager = this.getApplicationContext().getBean(AccessDecisionManager.class);
            CustomerFilterInvocationSecurityMetadataSource securityMetadataSource = this.getApplicationContext().getBean(CustomerFilterInvocationSecurityMetadataSource.class);
            securityMetadataSource.defaultAttributes("/login/**","/oauth/**","/test/**"); //不需要认证
            securityInterceptor.setSecurityMetadataSource(securityMetadataSource);
            securityInterceptor.setAccessDecisionManager(accessDecisionManager);
            System.out.println("执行时间init");
            web.securityInterceptor(securityInterceptor);
        });
    }
    
    /**
     * 配置HTTP安全信息
     * 主要是请求路径的允许
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.formLogin().permitAll()
                .and().authorizeRequests()
                .anyRequest().authenticated() //任何请求都需要认证
                .and().csrf().disable() //关闭csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
  • 相关阅读:
    使用cgroup控制内存
    计算材料学学习记录2
    c++ linux 配置
    C++实现四叉树索引
    vue.config.js 多环境配置
    【招聘岗位】基础设施软件开发人员
    MySQL基础练习,涉及建库、见表、约束、修改表结构等,超详细
    Centos8.1安装Redis6.2.5
    小学生的护眼灯哪个品牌最好?分享学生护眼台灯品牌
    【.Net】Linq的使用
  • 原文地址:https://blog.csdn.net/swg321321/article/details/126777169