Young87

SmartCat's Blog

So happy to code my life!

游戏开发交流QQ群号60398951

当前位置:首页 >跨站数据测试

SpringSecurity学习第四篇 授权

授权

什么是授权嗯?这里的授权不是说赋予你什么权限,而是说当你认证通过之后,你访问一个受保护的资源时,看你是否有这个权限,比如说对用户的操作,只可以管理员来执行,如果这个人是一个普通人就无法操作这些接口

授权流程
我们来看一下SpringSecurity中的授权流程。
1.当已认证的用户访问受保护的资源时,会被过滤器链(SecurityFilterChain) 中的FilterSecurityInterceptor的子类拦截
2. FilterSecurityInterceptor会从SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource获取当前访问的资源需要哪些角色(Collection< ConfigAttribute>)
3. 最后FilterSecurityInterceptor会调用AccessDecisionManager进行授权决策,看当前用户是否拥有操作这个资源所拥有的角色,若决策通过,则可以访问该接口

下面是AccessDecisionManager接口,decide方法就是定义你的授权逻辑,我们一般在这里面来判断当前用户是否拥有这个资源所需要的角色。 decide有三个参数,第一个Authentication:就是认证之后的身份
Object对象这里是要访问的受保护资源,他是一个FilterInvocation类型,你可以通过这个对象获取当前所访问的路径。Collection集合就是要操作当前资源,所需要的角色。
这里说一下:其实我们说资源,你可以认为他就是一个接口,

public interface AccessDecisionManager {
//  实现这个方法,定义决策逻辑
   void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;

   boolean supports(ConfigAttribute var1);

   boolean supports(Class<?> var1);
}

接下来上代码演示:
在上节的基础上,我们在增加两张表,分别是资源表,和资源角色关联表。这里我们权限是这样管理起来的。将操作资源的权限分配个角色,然后将角色很配给用户。一个资源可以被多个角色操作,一个人可以拥有多个不同的角色。当然了一个角色也是可以分给多个人的。这都是多对多的关系。

其实这里的权限,我们没有赋予它真实的含义,我们可以认为权限是一个动词。我们可以理解为权限是看用户是否拥有操作这个资源的能力的`操作
有这样五张表,menu就是定义系统中的资源,就是一些url规则,
在这里插入图片描述
在这里插入图片描述

上代码:
先看我们三个实体类
User类: 实现UserDetails
在这里插入图片描述

package org.javaboy.securitysql.bean;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

/**
 * @author wj
 * @version 1.0
 * @date 2020/5/23 15:31
 */
public class User implements  UserDetails {
    private Integer id;
    private String username;
    private String password;
    private boolean isEnabled;
    private List<Role> roles;

    /**
     * 用户所拥有的角色,这里需要将用户的角色写入这个集合中,
     * 最后可以通过Authentication获得到
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();

        for (Role role:roles) {
            /**
             *  public SimpleGrantedAuthority(String role) {
             *         Assert.hasText(role, "A granted authority textual representation is required");
             *         this.role = role;
             *     }
             */
            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
        }

        return simpleGrantedAuthorities;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", isEnabled=" + isEnabled +
                ", roles=" + roles +
                '}';
    }
}

Role: 角色类,只有三个字段,name角色名称,nameZh角色中文名称。注意一点,在SpringSecurity中角色要以大写的ROLE_开头,否则出错哦,下边是role表
在这里插入图片描述

package org.javaboy.securitysql.bean;

/**
 * @author wj
 * @version 1.0
 * @date 2020/5/23 15:52
 */
public class Role {
    private Integer id;
    private String name;
    private String nameZh;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameZh() {
        return nameZh;
    }

    public void setNameZh(String nameZh) {
        this.nameZh = nameZh;
    }

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", nameZh='" + nameZh + '\'' +
                '}';
    }
}

Menu:资源类,pattern是ant格式的匹配规则,这里面有一个List是看这个资源需要哪些角色才可以操作他

package org.javaboy.securitysql.bean;

import java.util.List;

/**
 * @author wj
 * @version 1.0
 * @date 2020/5/23 17:16
 */
public class Menu {
    private Integer id;
    private String pattern;
    private List<Role> roles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "menu{" +
                "id=" + id +
                ", pattern='" + pattern + '\'' +
                ", roles=" + roles +
                '}';
    }
}

接下来我们看一下UserService类,负责查询用户信息,loadUserByUsername是根据用户名查身份,
getRoles,根据用户id查到当前用户拥有哪些角色。sql的代码就不展示了

package org.javaboy.securitysql.service;

import org.javaboy.securitysql.bean.Role;
import org.javaboy.securitysql.bean.User;
import org.javaboy.securitysql.dao.UserMapeer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author wj
 * @version 1.0
 * @date 2020/5/23 15:37
 */
@Service
public class UserSerivce implements UserDetailsService {
    @Autowired
    UserMapeer userMapper;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(s);
        if(user ==null){
            return null;
        }
        List<Role> roles = userMapper.getRoles(user.getId());
        user.setRoles(roles);

        return user;
    }
}

关键点
此时我们需要定义两个最关键的类,一个类就是获取当前访问资源所需要的角色(FilterInvocationSecurityMetadataSource类型),一个类是决策类,看用户是否具有和这个角色,来进行访问控制(AccessDecisionManager)

首先我们创建一个FilterSecurityInvocationSecurityMetaDataSource的实现类,我们们通过这样获取当前访问路径拥有的角色的。我们通过ant匹配器,我们在数据库配置的是/user/*这样的路径,当我们访问/user/select的时候,那么他就匹配到了/user/ *, 此时先将所有的资源和他相应的角色查出来。然后将当前路径匹配到的ant资源对应的角色赋值到集合中去。

package org.javaboy.securitysql.filter;

import org.javaboy.securitysql.bean.Menu;
import org.javaboy.securitysql.bean.Role;
import org.javaboy.securitysql.dao.MenuMapper;
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.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * @author wj
 * @version 1.0
 * @date 2020/5/23 17:20
 */
@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    // ant路径格式匹配器
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Autowired
    MenuMapper menuMapper;
    /**
     * 当前访问资源所需要的角色
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

         // HttpServletRequest request = ((FilterInvocation) object).getRequest();
        // 访问的资源路径,
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        System.out.println(requestUrl);

        // 查询到所有的角色
        List<Menu> menus = menuMapper.selectAllMenu();

        // 通过ant匹配,查看当前路径所需要的角色
        for (Menu menu:menus){

            if(antPathMatcher.match(menu.getPattern(),requestUrl)){

                List<Role> roles = menu.getRoles();
                String[] roleStr = new String[roles.size()];

                for (int i = 0; i < roleStr.length; i++) {
                    roleStr[i]=roles.get(i).getName();
                }
                // 返回当前路径所需要的角色
                return SecurityConfig.createList(roleStr);

            }

        }

        // 如果没有匹配的话,让他登录即可访问,随便授予一个角色,不要给null,防止在别的地方空指针
        return SecurityConfig.createList("ROLE_login");
    }



    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

然后定义决策管理器,实现AccessDecisionManager类,decide的三个参数就是上面解释的那样,我们在这个方法中判断该用户使用有和这个资源所需要的角色

package org.javaboy.securitysql.filter;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * @author wj
 * @version 1.0
 * @date 2020/5/23 18:00
 */
@Component
public class MyAcccessDecisionManager implements AccessDecisionManager {

    /**
     *
     * @param authentication 当前认证的用户
     * @param o
     * @param collection  当前访问资源所需要的角色
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        // 当前用户所具有的角色
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

        for (ConfigAttribute configAttribute : collection) {
            // 已经约定好,直接放行,登录即可
            if(configAttribute.getAttribute().equals("ROLE_login")){
                return;
            }

            for (GrantedAuthority authority : authorities) {
                if(authority.getAuthority().equals(configAttribute.getAttribute())){
                    return;
                }
            }

        }
        // 如果有权限的话,就不会走到这,直接抛出异常
        throw new AccessDeniedException("非法请求,没有权限");

    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

最后一步就是我们的配置类了,这里主要是将我们定义的两个类设置进去】

 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {

                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                        o.setAccessDecisionManager(acccessDecisionManager);
                        return o;
                    }
                })
package org.javaboy.securitysql.config;

import org.javaboy.securitysql.filter.MyAcccessDecisionManager;
import org.javaboy.securitysql.filter.MyFilterInvocationSecurityMetadataSource;
import org.javaboy.securitysql.service.UserSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

/**
 * @author wj
 * @version 1.0
 * @date 2020/5/23 18:10
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserSerivce userSerivce;

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userSerivce);
    }

    @Autowired
    MyAcccessDecisionManager acccessDecisionManager;
    
    @Autowired
    MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
	// 安全配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {

                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                        o.setAccessDecisionManager(acccessDecisionManager);
                        return o;
                    }
                })
                .and()
                .formLogin()
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

总结: 授权操作主要是FilterSecurityIntercepor在工作。

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: 数据分析项目实战—信用卡客户违约概率预测

下一篇: C语言编程求解:1到1000之间所有的素数

精华推荐