美文网首页java
Spring Boot+Spring Security基于RBA

Spring Boot+Spring Security基于RBA

作者: 爱的旋转体 | 来源:发表于2018-11-13 13:02 被阅读0次

    一:RBAC:基于角色的权限访问控制,用5张表实现,用户、角色、权限、用户角色多对多关联表、角色权限多对多关联表。权限控制的核心是判断当前用户可以访问的所有URL是否包含当前访问的URL。
    二:自己写一个判断是否有权限的接口及实现

    package com.mycompany.myapp.service;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.security.core.Authentication;
    
    /**
    * @author xuzhipeng
    * @date 2018-11-09 16:52:43
    * @since 1.0
    */
    public interface RbacService {
    
         boolean hasPermission(HttpServletRequest request,Authentication authentication);
    
    }
    
    package com.mycompany.myapp.service.impl;
    
    import java.util.Collection;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component;
    import org.springframework.util.AntPathMatcher;
    
    import com.mycompany.myapp.service.RbacService;
    
    /**
    * @author xuzhipeng
    * @date 2018-11-09 16:54:43
    * @since 1.0
    */
    @Component("rbacService")
    public class RbacServiceImpl implements RbacService {
    
        private AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        @Override
        public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
            boolean hasPermission = false;
            Object principal = authentication.getPrincipal();
            if (principal instanceof UserDetails) {
                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                for(GrantedAuthority grantedAuthority : authorities){
                    if (antPathMatcher.match(grantedAuthority.getAuthority(), request.getRequestURI())) {
                        hasPermission = true;
                        break;
                  }
                }
            }
            return hasPermission;
        }
    
    }
    

    三:在Security的配置类中添加自定义的权限表达式。

    package com.mycompany.myapp.config;
    
    import javax.annotation.PostConstruct;
    
    import org.springframework.beans.factory.BeanInitializationException;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    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.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.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.web.filter.CorsFilter;
    import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
    
    import com.mycompany.myapp.security.AuthoritiesConstants;
    import com.mycompany.myapp.security.jwt.JWTConfigurer;
    import com.mycompany.myapp.security.jwt.TokenProvider;
    
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    @Import(SecurityProblemSupport.class)
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        private final AuthenticationManagerBuilder authenticationManagerBuilder;
    
        private final UserDetailsService userDetailsService;
    
        private final TokenProvider tokenProvider;
    
        private final CorsFilter corsFilter;
    
        private final SecurityProblemSupport problemSupport;
    
        public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
            this.authenticationManagerBuilder = authenticationManagerBuilder;
            this.userDetailsService = userDetailsService;
            this.tokenProvider = tokenProvider;
            this.corsFilter = corsFilter;
            this.problemSupport = problemSupport;
        }
    
        @PostConstruct
        public void init() {
            try {
                authenticationManagerBuilder
                    .userDetailsService(userDetailsService)
                    .passwordEncoder(passwordEncoder());
            } catch (Exception e) {
                throw new BeanInitializationException("Security configuration failed", e);
            }
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()
                .antMatchers(HttpMethod.OPTIONS, "/**")
                .antMatchers("/app/**/*.{js,html}")
                .antMatchers("/i18n/**")
                .antMatchers("/content/**")
                .antMatchers("/swagger-ui/index.html")
                .antMatchers("/test/**");
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .csrf()
                .disable()
                .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
            .and()
                .headers()
                .frameOptions()
                .disable()
            .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .authorizeRequests()
                .antMatchers("/api/register").permitAll()
                .antMatchers("/api/activate").permitAll()
                .antMatchers("/api/authenticate").permitAll()
                .antMatchers("/api/account/reset-password/init").permitAll()
                .antMatchers("/api/account/reset-password/finish").permitAll()
                .antMatchers("/api/**").access("@rbacService.hasPermission(request,authentication)")
                .antMatchers("/management/health").permitAll()
                .antMatchers("/management/info").permitAll()
                .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .and()
                .apply(securityConfigurerAdapter());
    
        }
    
        private JWTConfigurer securityConfigurerAdapter() {
            return new JWTConfigurer(tokenProvider);
        }
    }
    
    package com.mycompany.myapp.security.jwt;
    
    import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.web.DefaultSecurityFilterChain;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    
        private TokenProvider tokenProvider;
    
        public JWTConfigurer(TokenProvider tokenProvider) {
            this.tokenProvider = tokenProvider;
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            JWTFilter customFilter = new JWTFilter(tokenProvider);
            http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }
    
    package com.mycompany.myapp.security.jwt;
    
    import java.nio.charset.StandardCharsets;
    import java.security.Key;
    import java.util.*;
    import java.util.stream.Collectors;
    import javax.annotation.PostConstruct;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    import io.github.jhipster.config.JHipsterProperties;
    import io.jsonwebtoken.*;
    import io.jsonwebtoken.io.Decoders;
    import io.jsonwebtoken.security.Keys;
    
    @Component
    public class TokenProvider {
    
        private final Logger log = LoggerFactory.getLogger(TokenProvider.class);
    
        private static final String AUTHORITIES_KEY = "auth";
    
        private Key key;
    
        private long tokenValidityInMilliseconds;
    
        private long tokenValidityInMillisecondsForRememberMe;
    
        private final JHipsterProperties jHipsterProperties;
    
        public TokenProvider(JHipsterProperties jHipsterProperties) {
            this.jHipsterProperties = jHipsterProperties;
        }
    
        @PostConstruct
        public void init() {
            byte[] keyBytes;
            String secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret();
            if (!StringUtils.isEmpty(secret)) {
                log.warn("Warning: the JWT key used is not Base64-encoded. " +
                    "We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security.");
                keyBytes = secret.getBytes(StandardCharsets.UTF_8);
            } else {
                log.debug("Using a Base64-encoded JWT secret key");
                keyBytes = Decoders.BASE64.decode(jHipsterProperties.getSecurity().getAuthentication().getJwt().getBase64Secret());
            }
            this.key = Keys.hmacShaKeyFor(keyBytes);
            this.tokenValidityInMilliseconds =
                1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds();
            this.tokenValidityInMillisecondsForRememberMe =
                1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt()
                    .getTokenValidityInSecondsForRememberMe();
        }
    
        public String createToken(Authentication authentication, boolean rememberMe) {
            String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));
    
            long now = (new Date()).getTime();
            Date validity;
            if (rememberMe) {
                validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);
            } else {
                validity = new Date(now + this.tokenValidityInMilliseconds);
            }
    
            return Jwts.builder()
                .setSubject(authentication.getName())
                .claim(AUTHORITIES_KEY, authorities)
                .signWith(key, SignatureAlgorithm.HS512)
                .setExpiration(validity)
                .compact();
        }
    
        public Authentication getAuthentication(String token) {
            Claims claims = Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token)
                .getBody();
    
            Collection<? extends GrantedAuthority> authorities =
                Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());
    
            User principal = new User(claims.getSubject(), "", authorities);
    
            return new UsernamePasswordAuthenticationToken(principal, token, authorities);
        }
    
        public boolean validateToken(String authToken) {
            try {
                Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);
                return true;
            } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
                log.info("Invalid JWT signature.");
                log.trace("Invalid JWT signature trace: {}", e);
            } catch (ExpiredJwtException e) {
                log.info("Expired JWT token.");
                log.trace("Expired JWT token trace: {}", e);
            } catch (UnsupportedJwtException e) {
                log.info("Unsupported JWT token.");
                log.trace("Unsupported JWT token trace: {}", e);
            } catch (IllegalArgumentException e) {
                log.info("JWT token compact of handler are invalid.");
                log.trace("JWT token compact of handler are invalid trace: {}", e);
            }
            return false;
        }
    }
    
    package com.mycompany.myapp.security.jwt;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.GenericFilterBean;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    /**
     * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is
     * found.
     */
    public class JWTFilter extends GenericFilterBean {
    
        public static final String AUTHORIZATION_HEADER = "Authorization";
    
        private TokenProvider tokenProvider;
    
        public JWTFilter(TokenProvider tokenProvider) {
            this.tokenProvider = tokenProvider;
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String jwt = resolveToken(httpServletRequest);
            if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {
                Authentication authentication = this.tokenProvider.getAuthentication(jwt);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        private String resolveToken(HttpServletRequest request){
            String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
                return bearerToken.substring(7);
            }
            return null;
        }
    }
    

    四:在自己实现的UserDetailsService中loadUserByUsername方法中从数据库中查出当前用户可以访问的所有URL。

    package com.mycompany.myapp.security;
    
    import java.util.HashSet;
    import java.util.Locale;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    import org.apache.commons.lang3.StringUtils;
    import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    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.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.mycompany.myapp.domain.User;
    import com.mycompany.myapp.repository.UserRepository;
    
    /**
     * Authenticate a user from the database.
     */
    @Component("userDetailsService")
    public class DomainUserDetailsService implements UserDetailsService {
    
        private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class);
    
        private final UserRepository userRepository;
    
        public DomainUserDetailsService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @Override
        @Transactional
        public UserDetails loadUserByUsername(final String login) {
            log.debug("Authenticating {}", login);
    
            if (new EmailValidator().isValid(login, null)) {
                return userRepository.findOneWithAuthoritiesByEmail(login)
                    .map(user -> createSpringSecurityUser(login, user))
                    .orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
            }
    
            String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
            return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin)
    
                .map(user -> createSpringSecurityUser(lowercaseLogin, user))
                .orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
    
        }
    
        private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
            if (!user.getActivated()) {
                throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
            }
            Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
            user.getRoles().stream().forEach(role -> {
                grantedAuthorities.addAll(role.getMenus().stream().filter(menu -> StringUtils.isNotBlank(menu.getPermission())).map(menu -> new SimpleGrantedAuthority(menu.getPermission()))
                        .collect(Collectors.toList()));
            });
            return new org.springframework.security.core.userdetails.User(user.getLogin(),
                user.getPassword(),
                grantedAuthorities);
        }
    }
    

    相关文章

      网友评论

        本文标题:Spring Boot+Spring Security基于RBA

        本文链接:https://www.haomeiwen.com/subject/fzpkfqtx.html