SpringSecurity的HTTP权限控制

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_37338761/article/details/102728951

Spring Security的HTTP权限控制

简介

一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。

过滤器与核心组件

springSecurity在我们进行用户认证以及授予权限的时候,通过各种各样的拦截器来控制权限的访问,从而实现安全。

主要过滤器

过滤器 功能原理
WebAsyncManagerIntegrationFilter 提供了对securityContext和WebAsyncManager的集成
SessionManagementFilter 该过滤器会检测从当前请求处理开始到目前为止的过程中是否发生了用户登录认证行为(比如这是一个用户名/密码表单提交的请求处理过程),如果检测到这一情况,执行相应的session认证策略(一个SessionAuthenticationStrategy),然后继续继续请求的处理。
AnonymousAuthenticationFilter 匿名登录人过滤器,过滤器运行到AnonymousAuthenticationFilter时,如果SecurityContextHolder中持有的Authentication还是空的,则AnonymousAuthenticationFilter将创建一个AnonymousAuthenticationToken并存放在SecurityContextHolder中。在AuthenticationTrustResolverImpl判断一个SecurityContextHolder持有的Authentication是否AnonymousAuthenticationToken或RememberMeAuthenticationToken。如当ExceptionTranslationFilter捕获到一个AccessDecisionManager后就会使用它来判断当前Authentication对象是否为一个AnonymousAuthenticationToken,如果是则交由AuthenticationEntryPoint处理,否则将返回403错误码
UsernamePasswordAuthenticationFilter 登陆用户密码验证过滤器,对用户密码的正确性进行验证,认证失败就抛出异常,成功就返回Authentication对象。
RequestCacheAwareFilter Spring Security Web对请求提供了缓存机制,如果某个请求被缓存,它的提取和使用是交给RequestCacheAwareFilter完成的

框架的核心组件

组件 作用
SecurityContextHolder 提供对SecurityContext的访问
SecurityContext 其中可以包含多个AuthenticationProvider
ProviderManager 为AuthenticationManager接口的实现类
AuthenticationProvider 主要用来进行认证操作的类 调用其中的authenticate()方法去进行认证操作
Authentication Spring Security方式的认证主体
GrantedAuthority 对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示
UserDetails 构建Authentication对象必须的信息,可以自定义,可能需要访问DB得到
UserDetailsService 通过username构建UserDetails对象,通过loadUserByUsername根据 userName获取UserDetail对象 (可以在这里基于自身业务进行自定义的实现 如通过数据 库,xml,缓存获取等)

实战

在springboot中整合springSecurity

添加pom.xml依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

编写Java配置文件

自定义了一个springSecurity安全框架的配置类 继承WebSecurityConfigurerAdapter,重写其中的方法configure,在web容器启动的过程中该类实例对象会被WebSecurityConfiguration类处理。

import com.wqy.springbootes.security.AuthFilter;
import com.wqy.springbootes.security.AuthProvider;
import com.wqy.springbootes.security.LoginAuthFailHandler;
import com.wqy.springbootes.security.LoginUrlEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;



/**
 * Created by wqy.
 *
 * 密码:admin/admin
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * HTTP权限控制
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(authFilter(), UsernamePasswordAuthenticationFilter.class);

        // 资源访问权限
        http.authorizeRequests()
                .antMatchers("/admin/login").permitAll() // 管理员登录入口
                .antMatchers("/static/**").permitAll() // 静态资源
                .antMatchers("/user/login").permitAll() // 用户登录入口
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
                .antMatchers("/api/user/**").hasAnyRole("ADMIN",
                "USER")
                .and()
                .formLogin()
                .loginProcessingUrl("/login") // 配置角色登录处理入口
                .failureHandler(authFailHandler())
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/logout/page")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(urlEntryPoint())// AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
                .accessDeniedPage("/403");

        http.csrf().disable(); // 关闭默认打开的crsf protection
        http.headers().frameOptions().sameOrigin();// 支持SAMEORIGIN的设置方式
    }

    /**
     * 自定义认证策略
     */
    @Autowired
    public void configGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider()).eraseCredentials(true);
    }

   @Bean
    public AuthProvider authProvider() {
        return new AuthProvider();
    }

     @Bean
    public LoginUrlEntryPoint urlEntryPoint() {
        return new LoginUrlEntryPoint("/user/login");
    }

    @Bean
    public LoginAuthFailHandler authFailHandler() {
        return new LoginAuthFailHandler(urlEntryPoint());
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        AuthenticationManager authenticationManager = null;
        try {
            authenticationManager =  super.authenticationManager();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return authenticationManager;
    }

    @Bean
    public AuthFilter authFilter() {
        AuthFilter authFilter = new AuthFilter();
        authFilter.setAuthenticationManager(authenticationManager());
        authFilter.setAuthenticationFailureHandler(authFailHandler());
        return authFilter;
    }
}
  

解析:可以自定义哪些URL需要权限验证,哪些不需要。只需要在我们的SecurityConfig类中覆写configure(HttpSecurity http)方法即可。

方法解释:

  • http.authorizeRequests()方法有很多子方法,每个子匹配器将会按照声明的顺序起作用
  • 指定用户可以访问的多个url模式。特别的,任何用户可以访问以"/static"开头的url资源
  • 任何以"/admin"开头的请求限制用户具有 "ADMIN"角色。
  • 任何以"/user"、“/api/user/”开头的请求限制用户具有 "ADMIN"或“USER”角色。*

源码解析:

默认的configure(HttpSecurity http)方法继续向httpSecurity类中追加SecurityConfigurer的具体实现类,如authorizeRequests()方法追加一个ExpressionUrlAuthorizationConfigurer,formLogin()方法追加一个FormLoginConfigurer

其中ExpressionUrlAuthorizationConfigurer这个实现类比较重要,因为他会给我们创建一个非常重要的对象FilterSecurityInterceptor对象,FormLoginConfigurer对象比较简单,但是也会为我们提供一个在安全认证过程中经常用到会用的一个Filter:UsernamePasswordAuthenticationFilter

添加授权过滤器

public class AuthFilter extends UsernamePasswordAuthenticationFilter {

    @Autowired
    private IUserService userService;


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String name = obtainUsername(request);
        if(!Strings.isNullOrEmpty(name)){
            request.setAttribute("username",name);
            return super.attemptAuthentication(request, response);
        }

        User user = userService.findUserByUsername(name);

        if(Objects.equals(user.getUsername,name)){
            return new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
        }else{
            throw new BadCredentialsException("userCodeError");
        }

    }
}

解释:从request中取出authentication, authentication是在org.springframework.security.web.context.SecurityContextPersistenceFilter过滤器中通过捕获用户提交的登录表单中的内容生成的一个org.springframework.security.core.Authentication接口实例.

基于角色的登录入口控制器

public class LoginUrlEntryPoint extends LoginUrlAuthenticationEntryPoint {

    private static final String API_FREFIX = "/api";
    private static final String API_CODE_403 = "{\"code\": 403}";
    private static final String CONTENT_TYPE = "application/json;charset=UTF-8";

    private PathMatcher pathMatcher = new AntPathMatcher();
    private final Map<String, String> authEntryPointMap;

    public LoginUrlEntryPoint(String loginFormUrl) {
        super(loginFormUrl);
        authEntryPointMap = new HashMap<>();

        // 普通用户登录入口映射
        authEntryPointMap.put("/user/**", "/user/login");
        // 管理员登录入口映射
        authEntryPointMap.put("/admin/**", "/admin/login");
    }

    /**
     * 根据请求跳转到指定的页面,父类是默认使用loginFormUrl
     * @param request
     * @param response
     * @param exception
     * @return
     */
    @Override
    protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
                                                     AuthenticationException exception) {
        String uri = request.getRequestURI().replace(request.getContextPath(), "");

        for (Map.Entry<String, String> authEntry : this.authEntryPointMap.entrySet()) {
            if (this.pathMatcher.match(authEntry.getKey(), uri)) {
                return authEntry.getValue();
            }
        }
        return super.determineUrlToUseForThisRequest(request, response, exception);
    }

    /**
     * 如果是Api接口 返回json数据 否则按照一般流程处理
     * @param request
     * @param response
     * @param authException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        String uri = request.getRequestURI();
        if (uri.startsWith(API_FREFIX)) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType(CONTENT_TYPE);

            PrintWriter pw = response.getWriter();
            pw.write(API_CODE_403);
            pw.close();
        } else {
            super.commence(request, response, authException);
        }

    }
}

登录验证失败处理器

public class LoginAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
    private final LoginUrlEntryPoint urlEntryPoint;

    public LoginAuthFailHandler(LoginUrlEntryPoint urlEntryPoint) {
        this.urlEntryPoint = urlEntryPoint;
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        String targetUrl =
                this.urlEntryPoint.determineUrlToUseForThisRequest(request, response, exception);

        targetUrl += "?" + exception.getMessage();
        super.setDefaultFailureUrl(targetUrl);
        super.onAuthenticationFailure(request, response, exception);
    }
}

添加授权

public class AuthProvider implements AuthenticationProvider {
    @Autowired
    private IUserService userService;

    private final Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String userName = authentication.getName();
        String inputPassword = (String) authentication.getCredentials();

        User user = userService.findUserByName(userName);
        if (user == null) {
            throw new AuthenticationCredentialsNotFoundException("authError");
        }

        if (this.passwordEncoder.isPasswordValid(user.getPassword(), inputPassword, user.getId())) {
            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());

        }

        throw new BadCredentialsException("authError");

    }

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

解析:

拿到authentication对象后,过滤器会调用ProviderManager类的authenticate方法,并传入该对象.
ProviderManager类的authenticate方法再调用自身的doAuthentication方法,在doAuthentication方法中会调用类中的List<AuthenticationProvider> providers集合中的各个AuthenticationProvider接口实现类中的authenticate(Authentication authentication)方法进行验证

由此可见,真正的验证逻辑是由各个各个AuthenticationProvider接口实现类来完成的,DaoAuthenticationProvider类是默认情况下注入的一个AuthenticationProvider接口实现类.

参考文章:

Spring Security(11)——匿名认证
spring security源码分析之web包分析
springSecurity安全框架的学习和原理解读
Spring Security Web 5.1.2 源码解析 – RequestCacheAwareFilter
Spring Security Web 5.1.2 源码解析 – SessionManagementFilter
Spring Security Web 5.1.2 源码解析 – WebAsyncManagerIntegrationFilter

猜你喜欢

转载自blog.csdn.net/qq_37338761/article/details/102728951