springboot2.1.3整合spring-security,实现自定义授权决策

项目地址

项目地址:https://gitee.com/xuelingkang/spring-boot-demo
完整配置参考com.example.security包

maven依赖

<!-- security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

实现UserDetails

package com.example.model.vo;

import com.example.model.po.Resource;
import com.example.model.po.Role;
import com.example.model.po.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.jsonwebtoken.lang.Collections;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Data
@NoArgsConstructor
public class UserDetailsImpl implements UserDetails, Serializable {

    private static final long serialVersionUID = 1L;
    private User user;
    private String token;
    /** 登陆时间戳 */
    private Long loginTime;
    /** 过期时间戳 */
    private Long expireTime;

    public UserDetailsImpl(User user) {
        this.user = user;
    }

    @Override
    @JsonIgnore
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<SimpleGrantedAuthority> authorities = new HashSet<>();
        List<Role> roles = user.getRoles();
        if (!Collections.isEmpty(roles)) {
            for (Role role: roles) {
                List<Resource> resources = role.getResources();
                if (!Collections.isEmpty(resources)) {
                    for (Resource resource: resources) {
                        authorities.add(new SimpleGrantedAuthority(String.valueOf(resource.getId())));
                    }
                }
            }
        }
        return authorities;
    }
    @Override
    @JsonIgnore
    public String getPassword() {
        return user.getPassword();
    }
    @Override
    @JsonIgnore
    public String getUsername() {
        return user.getUsername();
    }
    // 账户是否未过期
    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }
    // 账户是否未锁定
    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }
    // 密码是否未过期
    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }
    // 账户是否激活
    @Override
    @JsonIgnore
    public boolean isEnabled() {
        return true;
    }

}

实现UserDetailsService

package com.example.service.impl;

import com.example.model.po.User;
import com.example.model.vo.UserDetailsImpl;
import com.example.params.Params;
import com.example.service.IUserService;
import org.apache.commons.lang.StringUtils;
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;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StringUtils.isEmpty(username)) {
            throw new UsernameNotFoundException("用户名username不能为空!");
        }
        User user = userService.customGetOne(new Params<>(new User().setUsername(username)));
        if (user==null) {
            throw new UsernameNotFoundException("用户名:"+ username + "不存在!");
        }
        return new UserDetailsImpl(user);
    }

}

主配置类

package com.example.config;

import com.example.filter.TokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler; //认证成功
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler; // 认证失败
    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler; // 登出成功
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint; // 认证异常
    @Autowired
    private AccessDeniedHandler accessDeniedHandler; // 授权失败
    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService; // 自己实现的UserDetailsService
    @Autowired
    private TokenFilter tokenFilter; // token过滤器,处理客户端的token参数
    @Autowired
    private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource; // 授权拦截器,根据url获取所需权限
    @Autowired
    private AccessDecisionManager accessDecisionManager; // 授权决策

    // 密码加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 定义认证用户信息获取来源,密码校验规则等
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    // 定义安全策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf
        http.csrf().disable();
        // 基于token,所以不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.authorizeRequests()
                // 放开所有路径
                .antMatchers("/**").permitAll()
                // 真正的授权决策
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                        o.setAccessDecisionManager(accessDecisionManager);
                        return o;
                    }
                });
        http.formLogin()
                .loginProcessingUrl("/login")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler);
        http.exceptionHandling()
                // 授权异常
                .accessDeniedHandler(accessDeniedHandler)
                // 认证异常
                .authenticationEntryPoint(authenticationEntryPoint);
        http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);

        // 解决不允许显示在iframe的问题
        http.headers().frameOptions().disable();
        http.headers().cacheControl();

        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

授权决策涉及的主要类

package com.example.security;

import com.example.model.po.Resource;
import com.example.service.IResourceService;
import io.jsonwebtoken.lang.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.List;

// 根据访问url获取所需权限
@Component
public class FilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource {

    @Value("${resource.type.http}")
    private String http;
    @Autowired
    private IResourceService resourceService;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();

        // 查询所有http类型的资源
        List<Resource> resources = resourceService.getByType(http);

        Resource resource = null;
        // 按照url匹配
        if (!Collections.isEmpty(resources)) {
            for (Resource r: resources) {
                RequestMatcher requestMatcher = new AntPathRequestMatcher(r.getResourcePattern(), r.getResourceMethod());
                if (requestMatcher.matches(request)) {
                    resource = r;
                }
            }
        }

        if (resource==null) {
            return null;
        }

        return SecurityConfig.createList(String.valueOf(resource.getId()));
    }

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

    @Override
    public boolean supports(Class<?> clazz) {
        return false;
    }

}

package com.example.security;

import com.example.service.ITokenService;
import io.jsonwebtoken.lang.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
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;
import java.util.List;

// 自定义授权决策
@Component
public class AccessDecisionManagerImpl implements AccessDecisionManager {

    @Autowired
    private ITokenService tokenService;

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        // 当前用户资源
        Collection<? extends GrantedAuthority> authorities;
        if (authentication instanceof AnonymousAuthenticationToken) {
            // 游客资源
            authorities = tokenService.getGuest().getAuthorities();
        } else {
            // 当前用户资源
            authorities = authentication.getAuthorities();
        }
        if (!Collections.isEmpty(configAttributes) && !Collections.isEmpty(authorities)) {
            // 需求资源id
            String resourceId = ((List<ConfigAttribute>) configAttributes).get(0).getAttribute();
            // 遍历当前角色资源
            for (GrantedAuthority authority: authorities) {
                if (authority.getAuthority().equals(resourceId)) {
                    // 如果匹配到表示用户有权限
                    return;
                }
            }
        }
        // 没有匹配到表示权限不足
        throw new AccessDeniedException("权限不足!");
    }

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

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

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值