security

Security

1、基本原理,三个基本的过滤器

security本质只是一个过滤器链

我通过三个最典型过滤器的源码来查看

  • FilterSecurityInterceptor: 是一个方法集的权限过滤器,基本位于过滤器的最低端
  • ExceptionTranslationFilter: 是一个异常过滤器,用来处理在认证授权过程中抛出的异常
  • UsernamePasswordAuthenticationFilter: 对/login的POST请求做拦截,检验表单中用户名,密码

1.1、FilterSecurityInterceptor

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   invoke(new FilterInvocation(request, response, chain));
}

我们来看一下他的invoke方法是怎么执行的

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
   if (isApplied(filterInvocation) && this.observeOncePerRequest) {
      // filter already applied to this request and user wants us to observe
      // once-per-request handling, so don't re-do security checking
      filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
      return;
   }
   // first time this request being called, so perform security checking
   if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
      filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
   }
   InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
   try {
      filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
   }
   finally {
      super.finallyInvocation(token);
   }
   super.afterInvocation(token, null);
}
  • InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
  • 这句话表示他这个过滤器之前还有过滤器,如果之前过滤器做了放行操作,那才往下继续执行,通过filterInvocation.getChain().doFilter执行

1.2、ExceptionTranslationFilter

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   try {
      chain.doFilter(request, response);
   }
   catch (IOException ex) {
      throw ex;
   }
   catch (Exception ex) {
      // Try to extract a SpringSecurityException from the stacktrace
      Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
      RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
            .getFirstThrowableOfType(AuthenticationException.class, causeChain);
      if (securityException == null) {
         securityException = (AccessDeniedException) this.throwableAnalyzer
               .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
      }
      if (securityException == null) {
         rethrow(ex);
      }
      if (response.isCommitted()) {
         throw new ServletException("Unable to handle the Spring Security Exception "
               + "because the response is already committed.", ex);
      }
      handleSpringSecurityException(request, response, chain, securityException);
   }
}
  • 异常处理的三个方法
    • 1、handleSpringSecurityException
    • 2、handleAuthenticationException
    • 3、handleAccessDeniedException

1.3、UsernamePasswordAuthenticationFilter

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException {
   if (this.postOnly && !request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
   }
   String username = obtainUsername(request);             //得到用户名
   username = (username != null) ? username : "";
   username = username.trim();							//对用户名进行修剪,去掉
   String password = obtainPassword(request);             //得到密码
   password = (password != null) ? password : "";
   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
   // Allow subclasses to set the "details" property
   setDetails(request, authRequest);
   return this.getAuthenticationManager().authenticate(authRequest);
}
  • 先判断是否是POST提交,然后得到用户的账号密码,用String类里的trim方法去除账号密码前后的空格,然后对其进行验证

  • 如果认证成功,他会去执行他的父类AbstractAuthenticationProcessingFilter中的successfulAuthentication方法

  • 如果认证失败,他会去执行中的unsuccessfulAuthentication方法

2、思考:过滤器链是如何进行加载的?

源码分析

1、使用SpringSecurity配置过滤器

  • DelegatingFilterProxy 翻译:授权 过滤器 代理
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {

   // Lazily initialize the delegate if necessary.
   Filter delegateToUse = this.delegate;
   if (delegateToUse == null) {
      synchronized (this.delegateMonitor) {
         delegateToUse = this.delegate;
         if (delegateToUse == null) {
            WebApplicationContext wac = findWebApplicationContext();
            if (wac == null) {
               throw new IllegalStateException("No WebApplicationContext found: " +
                     "no ContextLoaderListener or DispatcherServlet registered?");
            }
            delegateToUse = initDelegate(wac);
         }
         this.delegate = delegateToUse;
      }
   }

   // Let the delegate perform the actual doFilter operation.
   invokeDelegate(delegateToUse, request, response, filterChain);
}
  • delegateToUse = initDelegate(wac);
  • 通过initDelegate来进行初始化,我们点进它的源码
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
   String targetBeanName = getTargetBeanName();//***********FilterChainProxy
   Assert.state(targetBeanName != null, "No target bean name set");
   Filter delegate = wac.getBean(targetBeanName, Filter.class);//我们通过wac.getBean得到一个名字,  
   if (isTargetFilterLifecycle()) {
      delegate.init(getFilterConfig());
   }
   return delegate;
}
  • Filter delegate = wac.getBean(targetBeanName, Filter. Class);

  • 我们通过wac.getBean得到一个FilterChainProxy,我们找到他的源码

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
   if (!clearContext) {
      doFilterInternal(request, response, chain);
      return;
   }
   try {
      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
      doFilterInternal(request, response, chain);//************
   }
   catch (RequestRejectedException ex) {
      this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
   }
   finally {
      SecurityContextHolder.clearContext();
      request.removeAttribute(FILTER_APPLIED);
   }
}
  • doFilterInternal(request, response, chain);
  • 进入他的方法
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
   HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
   List<Filter> filters = getFilters(firewallRequest);
   if (filters == null || filters.size() == 0) {
      if (logger.isTraceEnabled()) {
         logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
      }
      firewallRequest.reset();
      chain.doFilter(firewallRequest, firewallResponse);//*************
      return;
   }
   if (logger.isDebugEnabled()) {
      logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
   }
   VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
   virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
  • List filters = getFilters(firewallRequest);
  • 这条语句只要是得到我们Security中所有的过滤器给他放到过滤链中,我们进入getFilters方法
private List<Filter> getFilters(HttpServletRequest request) {
   int count = 0;
   for (SecurityFilterChain chain : this.filterChains) {
      if (logger.isTraceEnabled()) {
         logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
               this.filterChains.size()));
      }
      if (chain.matches(request)) {
         return chain.getFilters();
      }
   }
   return null;
}
  • 对过滤器链进行一个迭代 ,然后用getFilter()方法对他进行一个加载,它里面有一个SecurityFilterChain,点进去发现有一个getFilter()的方法,点左边找到他的实现类
@Override
public List<Filter> getFilters() {
   return this.filters;
}

3、两个重要接口

3.1、UserDetailsService 接口:

查询数据库用户名和密码

  • 创建类继承 UsernamePasswordAuthenticationFilter 重写里面的三个方法

    • attemptAuthentication 方法得到用户名密码进行验证
    • successfulAuthentication
    • unsuccessfulAuthentication
  • 创建类实现 UserDetailsService,编写查询数据过程,返回user对象,这个user对象是安全框架提供对象

3.2、PassWordEncoder 接口:

数据加密接口,用于返回User对象里面密码加密

4.web权限认证

4.1、认证

1、设置登陆的用户名和密码三种方式

  • 通过配置文件 application.properties

    • spring.security.user.name=wangzhenwei
      spring.security.user.password=123456
      
  • 通过配置类

    • @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
              String password = passwordEncoder.encode("123");//加密
              auth.inMemoryAuthentication().passwordEncoder(passwordEncoder).withUser("wzw").password(password).roles("");
              //新版本要加上.passwordEncoder(passwordEncoder)不然报错
          }
      }
      
    • 如果不用.passwordEncoder(passwordEncoder),就可以选下面这种方式

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
              String password = passwordEncoder.encode("123");//加密
              auth.inMemoryAuthentication().withUser("wzw").password(password).roles("");
          }
          @Bean
          PasswordEncoder passwordEncoder(){
              return new BCryptPasswordEncoder();
          }
      }
      
  • 自定义编写实现类

    第一步:创建配置类,设置使用哪个UserDetailsService实现类

    @Configuration
    public class SecurityConfigDiy extends WebSecurityConfigurerAdapter {
        @Autowired
        private UserDetailsService userDetailsService;
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
        @Bean
        PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    

    第二部:编写实现类,返回User对象,User对象有用户名和操作权限

@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
		//这里的s就是你的用户名
        List<GrantedAuthority> auths= AuthorityUtils
            .commaSeparatedStringToAuthorityList(String.join("role"));
        return new User("wzw",new BCryptPasswordEncoder().encode("123"),auths);
    }
}
例子(这是我自己的项目写的)
  • SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;
    //认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }

    @Override//过滤静态资源
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/sys/**","/favicon.ico","/error");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling().accessDeniedPage("/login.html");
        http.sessionManagement()
                .maximumSessions(1)//允许同时登陆的人数
                .maxSessionsPreventsLogin(false)//false表示允许再次登录,之前的账号会被踢下线
                .expiredSessionStrategy(new CustomExpiredSessionStrategy());//
        http.cors();//允许跨域
        //退出登录   logoutSuccessUrl退出成功后去哪
        http.logout().
                logoutUrl("/logout")
                .logoutSuccessUrl("/login.html")
                .deleteCookies("JSESSIONID")
                .permitAll();
        //配置没有权限访问的页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");

        System.out.println(password());
        http.formLogin()
                    .loginPage("/login.html")//登录页面地址
                    .loginProcessingUrl("/user/login")//登录访问页面路径 action下的地址
                    .defaultSuccessUrl("/index", true)//登陆成功后,跳转路径
                    .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll()//设置哪些路径不需要认证
                .anyRequest().authenticated()//所有请求都要认证
                .and()
                    .csrf().disable()//关闭csrf防护
                    .headers().frameOptions().disable();//允许iframe页面嵌套
    }
}
  • MyUserDetailService
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        String encode = "$2a$10$5pHsIbuYPY1Kw7jyJYR6quJTgQ2U/RJX.TXqbnYm0fNHHi3FhPmN.";
        System.out.println(encode);
        List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList(String.join("role"));
        return new User("wzw",encode,auths);
    }
}

4.2、授权

基于角色或权限进行访问控制

四个方法:

4.2.1、hasAuthority 方法
  • 如果当前的主体具有指定的某一个权限,则返回ture,否则返回false

    • 1、在配置类 config 设置当前访问地址有哪些权限

      •  @Override
            protected void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests()
                        /*当前登录用户只有具有相应的权限才能访问接口*/
                        .antMatchers("接口").hasAuthority("权限");
            }
        
    • 2、在UserDetailsService,把返回User对象设置权限,授权

      • List<GrantedAuthority> auths= AuthorityUtils
            .commaSeparatedStringToAuthorityList(String.join("role"));
        
        return new User("wzw",encode,auths);
        
4.2.2、hasAnyAuthority

同理 config

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            /*当前登录用户只有具有相应的权限才能访问接口*/
            .antMatchers("接口").hasAnyAuthority("权限1,权限2")
}
4.2.3、hasRole
4.2.4、HasAnyRole

我们先看一下源码

private static String hasRole(String role) {
   Assert.notNull(role, "role cannot be null");
   Assert.isTrue(!role.startsWith("ROLE_"),
         () -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
   return "hasRole('ROLE_" + role + "')";
}

可以看出hasRole是要有"ROLE_"前缀

http.authorizeRequests()
        /*当前登录用户只有具有相应的角色才能访问接口*/
        .antMatchers("接口").hasAnyRole("角色")
        /*当前登录用户只有具有相应的角色才能访问接口*/
        .antMatchers("接口").hasRole("角色")
}
  • UserDetailsService
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList(String.join("ROLE_角色"));
return new User("wzw",encode,auths);
4.2.5、无权限页面跳转
http.exceptionHandling().accessDeniedPage("/403页面");

5、注解的使用

使用注解前要先开启注解功能

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)//开启
public class ValidatorApplication {

    public static void main(String[] args) {
        SpringApplication.run(ValidatorApplication.class, args);
    }

}

5.1、@Secured

  • 用户具有某个角色,可以访问方法

    @GetMapping("/test")
    @Secured({"ROLE_角色1","ROLE_角色2"})
    public String test(){
        return "successful";
    }
    

5.2、@PreAuthorize

  • @PreAuthorize注解适合进入方法前的权限验证, 可以将登录用户的roles/permissions参数传入方法中

  •     @GetMapping("/test")
    //    @PreAuthorize("hasRole('角色')")
    //    @PreAuthorize("hasAnyRole('角色1','角色2')")
    //    @PreAuthorize("hasAuthority('权限')")
    //    @PreAuthorize("hasAnyAuthority('权限1,权限2')")
        public String test(){
            return "successful";
        }
    

5.3、@PostAuthorize

  • @PostAuthorize注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限
  • 方法的使用和@PreAuthorize一样

5.4、@PostFilter

  • @PostFilter 注解是对方法返回数据进行一个过滤
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8gxFmFp-1633768328894)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20211009161326610.png)]

5.5、@PreFilter

  • @PreFilter 注解是对数据进入控制器之前对数据进行一个过滤

同理

6、用户注销

http.logout().logoutUrl("接口").logoutSuccessUrl("退出成功的页面").permitAll();

PreAuthorize(“hasAnyRole(‘角色1’,‘角色2’)”)
// @PreAuthorize(“hasAuthority(‘权限’)”)
// @PreAuthorize(“hasAnyAuthority(‘权限1,权限2’)”)
public String test(){
return “successful”;
}




### 5.3、@PostAuthorize

- @PostAuthorize注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限
- 方法的使用和@PreAuthorize一样

### 5.4、@PostFilter

- @PostFilter 注解是对方法返回数据进行一个过滤
- [外链图片转存中...(img-S8gxFmFp-1633768328894)]

### 5.5、@PreFilter

- @PreFilter 注解是对数据进入控制器之前对数据进行一个过滤

同理



## 6、用户注销

```java
http.logout().logoutUrl("接口").logoutSuccessUrl("退出成功的页面").permitAll();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值