Spring Boot Security配置用户认证和资源授权(URL与角色)

用户认证

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

查询用户(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){

    }
}

用户信息(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;
    }
}

安全配置(WebSecurityConfigurerAdapter)

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

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    //设置密码加密类
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

资源认证

资源认证,一般是基于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);
    }

}

访问决策管理

在获取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;
    }
}

安全拦截器(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);
    }
}

安全配置(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));
    }
}

第二种配置方法
无需创建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);
}

你可能感兴趣的:(#,SpringCloud,spring,boot,java,前端)