SpringSecurity笔记

SpringSecurity笔记,第1张

SpringSecurity笔记 1. Spring Security学习 1.1 简介
安全框架就是解决系统安全的框架。如果没有安全框架,我们需要手动的处理每个资源的访问控制,这是非常麻烦的。使用了安全框架,我们可以通过配置的方式实现对资源的访问限制。
1.2 核心功能
  • 认证 (你是谁)
认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
  • 授权 (你能干什么)
授权指的是验证某个用户是否有权限执行某个 *** 作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
  • 攻击防护 (防止伪造身份)
2. Spring Security 入门 2.1 权限管理中的相关概念 2.1.1 主体(principal)

principal:使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体

2.1.2 认证(authentication)

权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁。

笼统的认为就是以前所做的登录 *** 作。

2.1.3 授权(authorization)

将 *** 作系统的“权力”“授予”“主体”,这样主体就具备了 *** 作系统中特定功能的能力。

所以简单来说,授权就是给用户分配权限。

2.2 UserDetailsService相关API的介绍 2.2.1 UserDetailsService接口

如果要实现自定义登录逻辑

就需要实现UserDetailsService接口 重写loadUserByUsername方法

用户的登录是访问这个接口的唯一方法loadUserByUsername的,这个方法接收一个参数,就是用户名,如果没有,会抛异常。如果有,返回UserDetails。

2.2.2 UserDetails接口

如果 前端传递过来的username 与数据库中的username一致 那就返回UserDetails (但是UserDetails是一个接口 不能直接new一个UserDetails返回 所以Spring Security自身存在一个User类实现了UserDetails接口) 所以实际上返回的是User类。

如果不一致 那就抛出异常。

这个接口有7个抽象方法,值得注意的前3个方法,

第1个方法是获取权限的,第2个方法获取密码,第3个方法获取用户名。

2.2.3 User类

User类实现了UserDetails接口

最终登录成功 返回的是User类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hol7WSrj-1636258908002)(C:Users23652DesktopTypora个人文档SpringSecurityImageUser.png)]

User类的第一个构造方法

这个构造方法有3个参数:用户名、密码、权限。

然后里面调用了重载的第二个构造方法

User类的第二个构造方法

这个重载的构造方法有7个参数,除了用户名、密码、权限外,

还有账号是否启用、是否过期、是否锁定等。

小小的注意点:

方法参数 username

表示用户名。此值是客户端表单传递过来的数据。默认情况下必须叫username,否则无法接收。

(也就是前端传过来的参数名 默认情况下必须为username)

2.3 PasswordEncoder密码解析器 2.3.1 PasswordEncoder简介

PasswordEncoder是Spring Security提供的一个接口,称它为密码解析器,

这个接口主要是处理密码的。(也就是给密码加密)

2.3.2 PasswordEncoder接口方法

第一个方法: String encode(CharSequence rawPassword);

encode()方法是对明文密码进行加密的,返回一个密文。 (推荐使用SHA-1加密算法)

rawPassword参数是前端传递过来的密码

第二个方法: boolean matches(CharSequence rawPassword, String encodedPassword);

matches()是匹配明文密码和密文,返回布尔值。如果匹配成功 返回true 否则返回false

第一个参数rawPassword是明文密码 (也就是客户端传递过来的密码)

第二个参数encodedPassword是已经加密好的密码 (也就是存储在数据库中的密码)

第三个方法: default boolean upgradeEncoding(String encodedPassword);

upgradeEncoding()方法是对密文进行二次加密,这个方法是默认的。

encodedPassword参数是密文

2.3.3 BCryptPasswordEncoder类

PasswordEncoder接口有很多实现类,其中最主要的是官方推荐的BCryptPasswordEncoder类,平时使用的最多的就是这个密码解析器。BCryptPasswordEncoder是对bcrypt强散列方法的具体实现,是基于hash算法的单向加密。可以通过strength来控制强度,默认是10。

源码如下:

encode方法源码解析:

encode方法是对明文密码进行加密,原理是使用一个随机生成的salt,用明文密码加上这个salt来一起进行加密,返回密文,由于这个salt每次生成的都不一样,所以即使明文密码一样,最后加密出来的密文是不一样的,这样保证了用户密码的安全。

matchs方法源码解析:

matchs方法是用来匹配明文密码和密文的,最终结果用布尔值返回。
2.4 自定义登录逻辑 2.4.1 配置类

当我们自定义登录逻辑时,需要用到UserDetailsService和PasswordEncoder,Spring Security要求自定义登录逻辑时容器内必须要有PasswordEncoder实例,不能new出来,因此需要一个配置类来向容器中注入。

创建config包,包下定义SecurityConfig配置类:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

   // 向容器中注入PasswordEncoder实例
   @Bean
   public PasswordEncoder getPasswordEncoder() {
       return new BCryptPasswordEncoder();
   }

}
2.4.2 UserDetailsService的实现类

需要有一个类来实现UserDetailsService接口,如下:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
   
   @Autowired
   private PasswordEncoder passwordEncoder;

   
   @Override
   public UserDetails loadUserByUsername(String username) 
           throws UsernameNotFoundException {
       //实际是根据用户名去数据库查,这里就直接用静态数据了
       if(!username.equals("ycz")) {
           throw new UsernameNotFoundException("用户名不存在!");
       }
       //比较密码,匹配成功会返回UserDetails,实际上也会去数据库查
       String password = passwordEncoder.encode("ycz123456");
       User user = new User(username,password,AuthorityUtils.
               commaSeparatedStringToAuthorityList("ycz,admin"));
       return user;
   }

}
2.5 自定义登录页面

Spring Security提供了登录页面,就是需要验证的那个页面。但是一般在实际项目中会用自己定义好的登录页面,如果想用自己定义好的登录页面,比如这里的login.html,只需要修改配置类即可。

修改后的配置类如下: (Spring Security配置类 必须得继承WebSecurityConfigurerAdapter类 并重写configure方法)

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



 @Override
 protected void configure(HttpSecurity http) throws Exception {
     //表单提交
     http.formLogin()
     .loginPage("/login.html")//自定义登录页面
     //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
     .loginProcessingUrl("/login")
     //登录成功后跳转到的页面,只接受post请求
     .successForwardUrl("/toMain");

     //授权
     http.authorizeRequests()
          .antMatchers("/login.html").permitAll()//放行的路径
          .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

     //关闭csrf防护
     http.csrf().disable();
 }

 // 向容器中注入PasswordEncoder实例
 @Bean
 public PasswordEncoder getPasswordEncoder() {
     return new BCryptPasswordEncoder();
 }
}

LoginController修改如下:

一定要使用redirect进行重定向 跳转到首页 (main.html)

原因:(对于前后端不分离的项目)

由于浏览器不支持post请求 只支持get请求 而登录页面的form表单,只能发送post请求

所以form表单发送post请求 第一次请求(post请求)到达/login接口 返回HTTP状态码(302)之后 第二次请求(get请求)

重定向跳转到main.html页面

main.html页面如下:




   
   首页


登陆成功 跳转


@Controller
public class LoginController {

 @RequestMapping("/login")
 public String login(){
     return "redirect:main.html";
 }

 
 @RequestMapping("/toMain")
 public String main() {
     return "redirect:main.html";
 }
}

2.6 自定义错误页面

和自定义登录页面一样,修改配置类:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
   
   

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       //表单提交
       http.formLogin()
       .loginPage("/login.html")//自定义登录页面
       //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
       .loginProcessingUrl("/login")
       //登录成功后跳转到的页面,只接受post请求
       .successForwardUrl("/toMain")
       //登录失败后跳转到的页面,只接受post请求
       .failureForwardUrl("/toError");
       
       //授权
       http.authorizeRequests()
            .antMatchers("/login.html").permitAll()//放行的路径
            .antMatchers("/error.html").permitAll()
            .anyRequest().authenticated();//除了放行路径,其他路径都需要授权
       
       //关闭csrf防护
       http.csrf().disable();
   }

   // 向容器中注入PasswordEncoder实例
   @Bean
   public PasswordEncoder getPasswordEncoder() {
       return new BCryptPasswordEncoder();
   }

}

然后LoginController控制器添加:

   
   @RequestMapping("/toError")
   public String error() {
       return "redirect:error.html";
   }

自定义的错误页面error.html:




	
	
	错误页面


	登录失败,请重新登录!
	点击跳转



2.7 自定义用户名和密码参数名

前面写的登录页面的表单:




   
   login







默认情况下 用户名参数和密码参数 必须是username和password

提交方法必须是post

提交的地址必须是/login(要与Spring Security的配置文件的相关配置信息,保持一致)

通过这两个方法可以修改参数名称。

修改配置类,如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



  @Override
  protected void configure(HttpSecurity http) throws Exception {
      //表单提交
      http.formLogin()
       //自定义用户名密码参数名称
      .usernameParameter("username123")
      .passwordParameter("password123")
      .loginPage("/login.html")//自定义登录页面
      //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
      .loginProcessingUrl("/login")
      //登录成功后跳转到的页面,只接受post请求
      .successForwardUrl("/toMain")
      //登录失败后跳转到的页面,只接受post请求
      .failureForwardUrl("/toError");

      //授权
      http.authorizeRequests()
           .antMatchers("/login.html").permitAll()//放行的路径
           .antMatchers("/error.html").permitAll()
           .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

      //关闭csrf防护
      http.csrf().disable();
  }

  // 向容器中注入PasswordEncoder实例
  @Bean
  public PasswordEncoder getPasswordEncoder() {
      return new BCryptPasswordEncoder();
  }

}

然后form表单的参数名只需和配置类里设置的一样就可以了:




   
   login






2.8 自定义成功登录处理器 2.8.1 默认的successForwardUrl存在的问题

默认的.successForwardUrl("/toMain")跳转,存在的问题:

如下图:(源码)

如上图:(successForwardUrl源码)
successForwardUrl底层就是一个forward跳转
但forward跳转是无法跳到应用之外的页面的,不能满足我们实际开发的需求
2.8.2 自定义成功登录处理器实现

创建一个MyAuthenticationSuccessHandler类,实现AuthenticationSuccessHandler接口

要求:

1.定义一个私有的成员变量url

2.创建一个带url参数的构造器

3.重写onAuthenticationSuccess方法(把forward改成Redirect)

参考代码如下:

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

   private String url;

   public MyAuthenticationSuccessHandler(String url) {
       this.url = url;
   }

   @Override
   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {

   }


   //重写成功登录的跳转  把forward改成Redirect
   @Override
   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
       System.out.println(request.getRemoteAddr());
       // Collection authorities = authentication.getAuthorities();
       //需要强制类型转换成User
       User user = (User) authentication.getPrincipal();
       System.out.println(user.getUsername());
       //密码基于安全考虑  输出null
       System.out.println(user.getPassword());
       System.out.println(user.getAuthorities());
       response.sendRedirect(url);
   }
}

然后配置类里修改如下:

把.successForwardUrl("/toMain")修改为.successHandler(new MyAuthenticationSuccessHandler(“http://www.baidu.com”))

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



  @Override
  protected void configure(HttpSecurity http) throws Exception {
      //表单提交
      http.formLogin()
       //自定义用户名密码参数名称
      .usernameParameter("username123")
      .passwordParameter("password123")
      .loginPage("/login.html")//自定义登录页面
      //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
      .loginProcessingUrl("/login")
       //自定义登录成功处理器
      .successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
      //登录失败后跳转到的页面,只接受post请求
      .failureForwardUrl("/toError");

      //授权
      http.authorizeRequests()
           .antMatchers("/login.html").permitAll()//放行的路径
           .antMatchers("/error.html").permitAll()
           .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

      //关闭csrf防护
      http.csrf().disable();
  }

  // 向容器中注入PasswordEncoder实例
  @Bean
  public PasswordEncoder getPasswordEncoder() {
      return new BCryptPasswordEncoder();
  }

}
2.9 自定义失败登录处理器 2.9.1 默认的failureForwardUrl存在的问题

存在的问题,和默认的successForwardUrl存在的问题 类似

在此就不过多阐述

2.9.2 自定义失败登录处理器实现

创建一个MyAuthenticationFailureHandler类,实现AuthenticationFailureHandler接口

要求:

1.定义一个私有的成员变量url

2.创建一个带url参数的构造器

3.重写onAuthenticationFailure方法(把forward改成Redirect)

参考代码如下:

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
   private String url;

   public MyAuthenticationFailureHandler(String url) {
       this.url = url;
   }
   //重写登陆失败的跳转  把forward改成Redirect
   @Override
   public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
       response.sendRedirect(url);
   }
}

然后配置类里修改如下:

把.failureForwardUrl("/toError")修改为.failureHandler(new MyAuthenticationFailureHandler("/error.html"))

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



  @Override
  protected void configure(HttpSecurity http) throws Exception {
      //表单提交
      http.formLogin()
       //自定义用户名密码参数名称
      .usernameParameter("username123")
      .passwordParameter("password123")
      .loginPage("/login.html")//自定义登录页面
      //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
      .loginProcessingUrl("/login")
       //自定义登录成功处理器
      .successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
       //自定义登陆失败处理器
      .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

      //授权
      http.authorizeRequests()
           .antMatchers("/login.html").permitAll()//放行的路径
           .antMatchers("/error.html").permitAll()
           .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

      //关闭csrf防护
      http.csrf().disable();
  }

  // 向容器中注入PasswordEncoder实例
  @Bean
  public PasswordEncoder getPasswordEncoder() {
      return new BCryptPasswordEncoder();
  }

}

注意点:

事实上在实际开发中基本都是前后端分离的,使用的都是这两种处理器的方式来进行跳转。

2.10 关于授权配置 2.10.1 anyRequest

anyRequest代表的是所有请求,Spring Security的要求是这个(anyRequest)必须要放在最后面,如下:

可以理解为拦截器,放行路径放在前面,从前往后执行,除了放行路径之外的其他路径都需要进行认证才能访问。

2.10.2 antMatchers

这个是放行的路径,放行的路径不需要进行Spring Security即可访问,

比如项目的一些css、js、图片等静态资源全部需要放行,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJWclMuL-1636258908012)(C:Users23652DesktopTypora个人文档SpringSecurityImageantMatchers.png)]

注意点:
1.也可以使用ant匹配表达式,来控制放行路径
例:.antMatchers("*.png").permitAll()
2.使用正则表达式来匹配放行路径
例:.regexMatchers(".+[.]png").permitAll()
2.11 角色权限判断 2.11.1 基于权限判断

判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建User对象时指定的。

如下:

这里直接指定了两个静态的数据(admin,normal)作为权限

主页面(main.html)中加一个跳转:





首页


登陆成功 跳转


main1.html页面如下:





Title


权限控制!!!


修改Spring Security配置文件:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



 @Override
 protected void configure(HttpSecurity http) throws Exception {
     //表单提交
     http.formLogin()
     .loginPage("/login.html")//自定义登录页面
     //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
     .loginProcessingUrl("/login")
     //登录成功后跳转到的页面,只接受post请求
     .successForwardUrl("/toMain")
     //登录失败后跳转到的页面,只接受post请求
     .failureForwardUrl("/toError");

     //授权
     http.authorizeRequests()
          .antMatchers("/login.html").permitAll()//放行的路径
          .antMatchers("/error.html").permitAll()
           //权限控制  拥有admin权限才能访问main1.html
      	.antMatchers("/main1.html").hasAuthority("admin")
          .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

     //关闭csrf防护
     http.csrf().disable();
 }

 // 向容器中注入PasswordEncoder实例
 @Bean
 public PasswordEncoder getPasswordEncoder() {
     return new BCryptPasswordEncoder();
 }

}

**注意:**表示用户拥有admin这个权限,才能访问main1.html这个页面

还有一种情况,如果这个用户有多个权限的情况。

如下所示:(修改Spring Security配置文件)

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



 @Override
 protected void configure(HttpSecurity http) throws Exception {
     //表单提交
     http.formLogin()
     .loginPage("/login.html")//自定义登录页面
     //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
     .loginProcessingUrl("/login")
     //登录成功后跳转到的页面,只接受post请求
     .successForwardUrl("/toMain")
     //登录失败后跳转到的页面,只接受post请求
     .failureForwardUrl("/toError");

     //授权
     http.authorizeRequests()
          .antMatchers("/login.html").permitAll()//放行的路径
          .antMatchers("/error.html").permitAll()
           //权限控制  拥有admin权限才能访问main1.html
      	.antMatchers("/main1.html").hasAuthority("admin","ADMIN")
          .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

     //关闭csrf防护
     http.csrf().disable();
 }

 // 向容器中注入PasswordEncoder实例
 @Bean
 public PasswordEncoder getPasswordEncoder() {
     return new BCryptPasswordEncoder();
 }

}

注意:表示用户只需要拥有"admin","ADMIN"两个权限的其中之一,就可以访问main1.html页面

2.11.2 基于角色判断

解释:

如果用户具备给定角色就允许访问。否则出现 403。
参数取值来源于自定义登录逻辑 UserDetailsService实现类中创建 User 对象时给User赋予的授权。

判断用户是否有特定的角色,和用户的权限一样,

用户的角色也是在自定义登录逻辑中创建User对象时指定的。

修改UserDetailsService的实现类:

注意:角色定义必须以ROLE_开头,后面跟上角色名称,这是固定格式。

例:ROLE_abc 的角色名称是abc

修改Spring Security配置文件:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



 @Override
 protected void configure(HttpSecurity http) throws Exception {
     //表单提交
     http.formLogin()
     .loginPage("/login.html")//自定义登录页面
     //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
     .loginProcessingUrl("/login")
     //登录成功后跳转到的页面,只接受post请求
     .successForwardUrl("/toMain")
     //登录失败后跳转到的页面,只接受post请求
     .failureForwardUrl("/toError");

     //授权
     http.authorizeRequests()
          .antMatchers("/login.html").permitAll()//放行的路径
          .antMatchers("/error.html").permitAll()
           //权限控制  拥有admin权限才能访问main1.html
      		//.antMatchers("/main1.html").hasAuthority("admin","ADMIN")
         	//访问main1.html  必须得拥有abc角色   角色名称不能加ROLE_前缀
          .antMatchers("/main1.html").hasRole("abc")
          .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

     //关闭csrf防护
     http.csrf().disable();
 }

 // 向容器中注入PasswordEncoder实例
 @Bean
 public PasswordEncoder getPasswordEncoder() {
     return new BCryptPasswordEncoder();
 }

}

还有一种情况,如果这个用户有多个角色。

处理方式和上述的基于权限的处理方式类似。

.antMatchers("/main1.html").hasRole("abc")//修改为
.antMatchers("/main1.html").hasAnyRole("abc","ABC")//类似于这样子的情况,用户拥有权限abc,ABC
2.11.3 基于IP地址控制

只允许指定的IP地址访问,其他的IP拒绝访问,如下:

修改Spring Security配置文件:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{



@Override
protected void configure(HttpSecurity http) throws Exception {
    //表单提交
    http.formLogin()
    .loginPage("/login.html")//自定义登录页面
    //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
    .loginProcessingUrl("/login")
    //登录成功后跳转到的页面,只接受post请求
    .successForwardUrl("/toMain")
    //登录失败后跳转到的页面,只接受post请求
    .failureForwardUrl("/toError");

    //授权
    http.authorizeRequests()
         .antMatchers("/login.html").permitAll()//放行的路径
         .antMatchers("/error.html").permitAll()
          //权限控制  拥有admin权限才能访问main1.html
     	  .antMatchers("/main1.html").hasAuthority("admin","ADMIN")
         //基于ip地址
         .antMatchers("/main1.html").hasIpAddress("127.0.0.1")
         .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

    //关闭csrf防护
    http.csrf().disable();
}

// 向容器中注入PasswordEncoder实例
@Bean
public PasswordEncoder getPasswordEncoder() {
    return new BCryptPasswordEncoder();
}

}

也就是添加,.antMatchers("/main1.html").hasIpAddress(“127.0.0.1”)这一段代码

2.12 自定义403处理方案

http状态码403出现的原因:用户权限不足。

实现步骤:

第一步:新建MyAccessDeniedHandler类,实现AccessDeniedHandler 接口

第二步:重写AccessDeniedHandler 接口的handle方法

第三步:在Spring Security配置类中,添加相对应的配置。

自定义的MyAccessDeniedHandler类,代码如下:

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
      //响应状态
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);//设置状态码 403
      //设置返回数据格式  json字符串
      response.setHeader("Content-Type","application/json;charset=utf-8");
      PrintWriter printWriter=response.getWriter();
      printWriter.write("{"status":"error","msg":"权限不足,请联系管理员"}");
      printWriter.flush();
      printWriter.close();

  }
}

修改Spring Security配置文件:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{


//注入异常处理的实现类
  @Autowired
  private MyAccessDeniedHandler myAccessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
   //表单提交
   http.formLogin()
   .loginPage("/login.html")//自定义登录页面
   //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
   .loginProcessingUrl("/login")
   //登录成功后跳转到的页面,只接受post请求
   .successForwardUrl("/toMain")
   //登录失败后跳转到的页面,只接受post请求
   .failureForwardUrl("/toError");

   //异常处理
   http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
   //授权
   http.authorizeRequests()
        .antMatchers("/login.html").permitAll()//放行的路径
        .antMatchers("/error.html").permitAll()
         //权限控制  拥有admin权限才能访问main1.html
    	  .antMatchers("/main1.html").hasAuthority("admin","ADMIN")
        //基于ip地址
        .antMatchers("/main1.html").hasIpAddress("127.0.0.1")
        .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

   //关闭csrf防护
   http.csrf().disable();
}

// 向容器中注入PasswordEncoder实例
@Bean
public PasswordEncoder getPasswordEncoder() {
   return new BCryptPasswordEncoder();
}

}

添加 :http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);这一段代码(异常处理)

2.13 基于表达式的访问控制 2.13.1 内置的access()方法

先看一下之前的基于角色、权限、IP地址以及内置的访问控制的底层:

access表达式,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WYbIUtsc-1636258908016)(C:Users23652DesktopTypora个人文档SpringSecurityImageaccess表达式.png)]

测试access表达式

修改Spring Security配置文件:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{


//注入异常处理的实现类
 @Autowired
 private MyAccessDeniedHandler myAccessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
  //表单提交
  http.formLogin()
  .loginPage("/login.html")//自定义登录页面
  //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
  .loginProcessingUrl("/login")
  //登录成功后跳转到的页面,只接受post请求
  .successForwardUrl("/toMain")
  //登录失败后跳转到的页面,只接受post请求
  .failureForwardUrl("/toError");

  //异常处理
  http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
  //授权
  http.authorizeRequests()
       .antMatchers("/login.html").access("permitAll")//放行的路径
       .antMatchers("/error.html").access("permitAll")//放行的路径
        //权限控制  拥有admin权限才能访问main1.html
   	 .antMatchers("/main1.html").hasAnyRole("admin","ADMIN")
       //基于ip地址
       .antMatchers("/main1.html").hasIpAddress("127.0.0.1")
       .anyRequest().authenticated();//除了放行路径,其他路径都需要授权

  //关闭csrf防护
  http.csrf().disable();
}

// 向容器中注入PasswordEncoder实例
@Bean
public PasswordEncoder getPasswordEncoder() {
  return new BCryptPasswordEncoder();
}

}

2.13.2 自定义方法

在实际项目中很有可能出现需要自己自定义逻辑的情况。比如判断登录用户是否具有访问当前URL权限。

实现步骤:

第一步:新建接口(MyService),创建hasPermission方法

public interface MyService {

   public boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

第二步:新建实现类,继承MyService接口,重写hasPermission方法

@Service
public class MyServiceImpl implements MyService {

   
   @Override
   public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
       //获取主体
       Object obj = authentication.getPrincipal();
       //判断主体是否属于UserDetails
       if(obj instanceof UserDetails)
       {
           //强制类型转换
           UserDetails userDetails=(UserDetails)obj;
           //获取权限
           Collection authorities = userDetails.getAuthorities();
           //判断请求的URI是否在权限里面
           return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
       }

       return false;
   }
}

第三步:

修改Spring Security配置文件:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{


//注入异常处理的实现类
 @Autowired
 private MyAccessDeniedHandler myAccessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
  //表单提交
  http.formLogin()
  .loginPage("/login.html")//自定义登录页面
  //必须和表单提交的接口路径一致,会去执行自定义登录逻辑
  .loginProcessingUrl("/login")
  //登录成功后跳转到的页面,只接受post请求
  .successForwardUrl("/toMain")
  //登录失败后跳转到的页面,只接受post请求
  .failureForwardUrl("/toError");

  //异常处理
  http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
  //授权
  http.authorizeRequests()
       .antMatchers("/login.html").access("permitAll")//放行的路径
       .antMatchers("/error.html").access("permitAll")//放行的路径
        //权限控制  拥有admin权限才能访问main1.html
   	 .antMatchers("/main1.html").hasAnyRole("admin","ADMIN")
       //基于ip地址
       .antMatchers("/main1.html").hasIpAddress("127.0.0.1")
       .anyRequest().authenticated();//除了放行路径,其他路径都需要授权
   	//自定义access方法
       .anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");

  //关闭csrf防护
  http.csrf().disable();
}

// 向容器中注入PasswordEncoder实例
@Bean
public PasswordEncoder getPasswordEncoder() {
  return new BCryptPasswordEncoder();
}

}

添加.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");这一段配置

第四步:

在自定义登录逻辑的实现类(UserDetailServiceImpl)添加权限,(也就是添加/main.html权限)

//自定义登录逻辑
@Service
public class UserDetailServiceImpl implements UserDetailsService {


   //注入passwordEncoder
   @Autowired
   private PasswordEncoder passwordEncoder;
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       System.out.println("执行自定义登录逻辑");
       //1.根据用户名去数据库查询 如果不存在抛UsernameNotFoundException异常
       if(!"admin".equals(username))
       {
           throw new UsernameNotFoundException("用户名不存在");
       }
       //2.比较密码 (注册时已经加密过) 如果匹配成功 返回UserDetails
       //AuthorityUtils.commaSeparatedStringToAuthorityList为权限
       String password = passwordEncoder.encode("123");
       // /main1.html 是request.getRequestURI()对应的结果
       return new User(username,password,
               AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc,/main.html"));//设置角色必须以ROLE_开头 角色名称为abc
   }
}

后续内容会继续更新

敬请期待。。。。。。

本人学生党一枚,第一次写博客。如有问题,麻烦各位大哥,高抬贵手,见谅!

Encoder实例

@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}

}

添加.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");这一段配置



第四步:

在自定义登录逻辑的实现类(UserDetailServiceImpl)添加权限,(也就是添加/main.html权限)

```java
//自定义登录逻辑
@Service
public class UserDetailServiceImpl implements UserDetailsService {


   //注入passwordEncoder
   @Autowired
   private PasswordEncoder passwordEncoder;
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       System.out.println("执行自定义登录逻辑");
       //1.根据用户名去数据库查询 如果不存在抛UsernameNotFoundException异常
       if(!"admin".equals(username))
       {
           throw new UsernameNotFoundException("用户名不存在");
       }
       //2.比较密码 (注册时已经加密过) 如果匹配成功 返回UserDetails
       //AuthorityUtils.commaSeparatedStringToAuthorityList为权限
       String password = passwordEncoder.encode("123");
       // /main1.html 是request.getRequestURI()对应的结果
       return new User(username,password,
               AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc,/main.html"));//设置角色必须以ROLE_开头 角色名称为abc
   }
}

后续内容会继续更新

敬请期待。。。。。。

本人学生党一枚,第一次写博客。如有问题,麻烦各位大哥,高抬贵手,见谅!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5137304.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-17
下一篇 2022-11-17

发表评论

登录后才能评论

评论列表(0条)

保存