Spring Boot整合Spring security

Spring security使用

Spring security

一、SpringSecurity简介

​ Spring Security是一个高度自定义的安全框架。利用Spring IoC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。

二、创建SpringSecurity项目(与SpringBoot整合)

SpringSecurity已经被SpringBoot集成,只需要引入启动器即可,如下所示:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>

<dependencies>
    <!--Security的启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

引入启动器后,SpringSecurity就已经生效了,默认拦截所有请求,如果用户没有登录,访问http://localhost:8080/会跳转到内置的登陆页面,如下所示:

在这里插入图片描述

默认的登陆用户名是user,密码是控制台输出的密码:

在这里插入图片描述
在这里插入图片描述

然后,我们在浏览器中输入账号和密码后会显示404错误内容,我们认证通过但是,并没有找到控制器,所以给出404错误。

三、UserDetailsService详解

在实际应用中,用户名和密码是从数据库中查询出来的,UserDetailsService接口可以让我们自定义认证逻辑(实现UserDetailsService接口,然后将实现类交由spring管理即可),从而不需要框架自定义的username和密码。
在这里插入图片描述

1、方法的介绍

方法的作用:根据用户名加载用户

2、返回值

返回值是UserDetails,是一个接口,定义如下:

在这里插入图片描述

此接口的实现类有:

在这里插入图片描述

我们只需要其中的User类,User类的全限定名为:

org.springframework.security.core.userdetails.User

注意:不要与自己定义的User类搞混了

3、方法参数

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

4、异常

UsernameNotFoundException 用户名没有发现异常。在loadUserByUsername中是需要通过自己的逻辑从数据库中取值的。如果通过用户名没有查询到对应的数据,应该抛出UsernameNotFoundException,系统就知道用户名没有查询到。

代码如下(这里添加了从数据库根据用户名查询用户的这一步骤,自行添加相关依赖(mybatis、mysql),创建model,mapper):

@Component
public class MyAuth implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("客户端输入的username:"+username);

        //连接数据库操作,进行校验
        com.java.pojo.User user = userMapper.selectUserByUsername(username);

        if(user == null){
            throw new UsernameNotFoundException("用户名不存在!!!");
        }

        /**
         * security的user对象的参数(String username, String password,
         *           Collection<? extends GrantedAuthority> authorities)解释:
         *  username:用户名
         *  password:密码
         *  authorities:是一个集合,表示授权的集合,参数不能为null,为null会抛异常
         *  ( 否则抛异常 org.springframework.security.authentication.InternalAuthenticationServiceException: Cannot pass a null GrantedAuthority collection)
         *  我们通过权限工具类将权限字符串生成一个authorities集合即可
         */

        return new User(user.getUsername(),user.getPwd(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,guest,admin"));
    }
}

项目启动报错:没有密码解析器,所以我们需要创建一个密码解析器

在这里插入图片描述

四、PasswordEncoder密码解析器详解

我们有了用户名还不行,一个完整的登陆信息还需要包含密码,我们自定义了用户名还需要自定义密码解析器,不然SpringSecurity无法解析我们前台输入的明文密码(SpringSecurity有多个内置的密码解析器,SpringSecurity所展示出来的密码都是单向加密后的密文)

1、PasswordEncoder接口介绍

encode():把参数按照特定的解析规则进行解析。

matches():验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。

upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回true,否则返回false。默认返回false。

在这里插入图片描述

2、内置解析器介绍

在这里插入图片描述

3、BCryptPasswordEncoder简介

BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。

BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10.

在这里我们只需要将BCryptPasswordEncoder注入到spring的容器中即可,创建配置类,配置类中有个方法返回BCryptPasswordEncoder对象,方法上添加@Bean注解,代码如下:

在这里插入图片描述

现在我们启动项目就不会报错,我们在浏览器中输入账号和密码后会显示404错误内容,我们认证通过但是,并没有找到控制器,所以给出404错误。

五、自定义登陆页面

虽然Spring Security给我们提供了登录页面,但是对于实际项目中,大多喜欢使用自己的登录页面。所以Spring Security中不仅仅提供了登录页面,还支持用户自定义登录页面。实现过程也比较简单,只需要修改配置类即可。

1、编写登陆页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<!--没有对应的控制器(action)也可以-->
<form method="post">
    用户名:<p><input type="text" name="username"/></p>
    密码:<p><input type="password" name="password"/></p>
    <p><input type="submit" value="登录"/></p>
</form>
</body>
</html>

2、修改配置类

继承WebSecurityConfigurerAdapter抽象类,实现抽象方法configure(这个方法主要是用于设置页面、设置认证逻辑以及权限判断用的),代码如下:

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 路径认证控制器
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * formLogin(): 表示手动配置认证逻辑
         * loginProcessingUrl() : 设置登陆页面的表单提交路径,可以不真实存在
         * successForwardUrl() : 设置登陆成功后的请求路径
         * loginPage() : 设置登陆页面
         */
        http.formLogin()
                .loginProcessingUrl("/login")
                .successForwardUrl("/main")
                .loginPage("/login.html");
        
        //authorizeRequests() : 设置请求权限
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated();

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

    /**
     * 密码凭证器
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3、编写控制器代码

@RequestMapping(value = {"/","/main"})
@ResponseBody
public String index(){
    return "ok";
}

六、认证过程其他配置

1、失败跳转

1、1:编写失败页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
操作失败,请重新登录. <a href="/login.html">跳转</a>
</body>
</html>
1、2:修改配置类
http.formLogin()
        .loginProcessingUrl("/login")
        .failureForwardUrl("/fail") //添加跳转失败路径
        .successForwardUrl("/main")
        .loginPage("/login.html");
1、3:添加控制类方法
@RequestMapping("/fail")
public String toLogin(){
    return "redirect:/fail.html";
}
1、4:设置fail.html不需要认证
.antMatchers("/login.html", "/fail.html").permitAll() //将/fail.html添加进去即可

2、设置请求账户和密码的参数名

当进行登录时会执行UsernamePasswordAuthenticationFilter过滤器。

UsernamePasswordAuthenticationFilter登录认证部分源码如下:

public class UsernamePasswordAuthenticationFilter extends
      AbstractAuthenticationProcessingFilter {

   //SpringSecurity内部定义的登录表单input的name属性的值,分别是username、password
   //登录提交的数据的名称必须与之对应
   //如果登录表单提交时,input的name属性的值不为username、password,则SpringSecurity
   //无法接受表单提交的数据
   public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
   public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

   private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
   private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
   
   //postOnly=true:默认情况下只允许POST请求。
   private boolean postOnly = true;

   //当前方法给定了登录认证时action的值,以及表单的提交方式
   public UsernamePasswordAuthenticationFilter() {
      super(new AntPathRequestMatcher("/login", "POST"));
   
   //当前方法就是登录认证的核心方法
   public Authentication attemptAuthentication(HttpServletRequest request,
         HttpServletResponse response) throws AuthenticationException {
      
      //判断表单提交的方式是否为post,不是则抛出异常
      if (postOnly && !request.getMethod().equals("POST")) {
         throw new AuthenticationServiceException(
               "Authentication method not supported: " + request.getMethod());
      }

      //obtainUsername():底层就是通过request.getParameter(usernameParameter)获取的
      // usernameParameter就是当前类定义的常量
      String username = obtainUsername(request);
      //与上面的username一样
      String password = obtainPassword(request);

      if (username == null) {
         username = "";
      }

      if (password == null) {
         password = "";
      }

      username = username.trim();

      UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
            username, password);

      // Allow subclasses to set the "details" property
      setDetails(request, authRequest);

      //这个就是认证的具体细节了,有兴趣的可以自己去看看
      return this.getAuthenticationManager().authenticate(authRequest);
   }

修改页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="/login" method="post">
    用户名:<p><input type="text" name="name"/></p>
    密码:<p><input type="password" name="pwd"/></p>
    <p><input type="submit" value="登录"/></p>
</form>
</body>
</html>

修改配置类

http.formLogin()
        .loginProcessingUrl("/login")
        .passwordParameter("pwd") 
        .usernameParameter("name")
        .failureForwardUrl("/fail")
        .successForwardUrl("/main")
        .loginPage("/login.html");

七、访问控制url匹配

上面只是简单的使用了下请求权限的用法,下面我们将对请求权限简单的介绍一下。

在配置类中http.authorizeRequests()主要是对url进行控制,也就是我们所说的授权(访问控制)。http.authorizeRequests()也支持连缀写法,总体公式为:

url匹配规则.权限控制方法

通过上面的公式可以有很多url匹配规则和很多权限控制方法。这些内容进行各种组合就形成了Spring Security中的授权。

在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果,越是具体的应该放在前面,越是笼统的应该放到后面。

1、anyRequest()

在之前认证过程中我们就已经使用过anyRequest(),表示匹配所有的请求。

2、antMatcher()

参数是不定向参数,每个参数是一个ant表达式,用于匹配URL规则。

规则如下:

? 匹配一个字符

* 匹配0个或多个字符

** 匹配0个或多个目录

在实际项目中经常需要放行所有静态资源,下面演示放行js文件夹下所有脚本文件。

.antMatchers("/js/**").permitAll()
3、regexMatchers()

使用正则表达式进行匹配。和antMatchers()主要的区别就是参数,antMatchers()参数是ant表达式,regexMatchers()参数是正则表达式。

演示所有以.js结尾的文件都被放行。

.regexMatchers(**".+[.]js"**).permitAll()

无论是antMatchers()还是regexMatchers()都具有两个参数的方法,其中第一个参数都是HttpMethod,表示请求方式,当设置了HttpMethod后表示只有设定的特定的请求方式才执行对应的权限设置。

枚举类型HttpMethod内置属性如下:

在这里插入图片描述

八、内置访问控制方法介绍(常用的)

Spring Security匹配了URL后调用了permitAll()表示不需要认证,随意访问。在Spring Security中提供了多种内置控制。

1、permitAll()

permitAll()表示所匹配的URL任何人都允许访问。

2、authenticated()

authenticated()表示所匹配的URL都需要被认证才能访问。

3、anonymous()

anonymous()表示可以匿名访问匹配的URL。和permitAll()效果类似,只是设置为anonymous()的url会执行filter 链中。认证后就无法访问anonymous()修饰的页面

4、denyAll()

denyAll()表示所匹配的URL都不允许被访问。

5、rememberMe()

被“remember me”的用户允许访问

6、fullyAuthenticated()

如果用户不是被remember me的,才可以访问。

九、角色权限判断

除了之前讲解的内置权限控制。Spring Security中还支持很多其他权限控制。这些方法一般都用于用户已经被认证后,判断用户是否具有特定的要求。

1、hasAuthority(String)

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

在配置类中通过hasAuthority(“/user”)设置具有“/user”权限时才能访问。

.antMatchers("/user.html").hasAuthority("/user")

2、hasAnyAuthority(String…)

如果用户具备给定权限中某一个,就允许访问。注意,是区分大小写的,下面代码中由于认证具有“/product”与“/order”,所以用户可以访问/product.html

.antMatchers("/product.html").hasAnyAuthority("/product", "/order")

3、hasRole(String)

如果用户具备给定角色就允许访问。否则出现403。

参数取值来源于自定义登录逻辑UserDetailsService实现类中创建User对象时给User赋予的授权。

在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。例如:ROLE_abc 其中abc是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写abc即可。否则启动报错。

.antMatchers("/system.html").hasRole("管理员")

4、hasAnyRole(String…)

如果用户具备给定角色的任意一个,就允许被访问

5、hasIpAddress(String)

如果请求是指定的IP就运行访问。

可以通过request.getRemoteAddr()获取ip地址。

需要注意的是在本机进行测试时localhost和127.0.0.1输出的ip地址是不一样的。

当浏览器中通过localhost进行访问时控制台打印的内容:

在这里插入图片描述

当浏览器中通过127.0.0.1访问时控制台打印的内容:

在这里插入图片描述

当浏览器中通过具体ip进行访问时控制台打印内容:

在这里插入图片描述

十、自定义403处理方案

使用Spring Security时经常会看见403(无权限),默认情况下显示的效果如下:

在这里插入图片描述

而在实际项目中可能都是一个异步请求,显示上述效果对于用户就不是特别友好了。Spring Security支持自定义权限受限。

解决方式一

1、1:新建类实现AccessDeniedHandler
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
        out.flush();
        out.close();
    }
}
1、2:修改配置文件

配置类中重点添加异常处理器。设置访问受限后交给哪个对象进行处理。

myAccessDeniedHandler是在配置类中进行自动注入的。

//异常处理 http.exceptionHandling() 
.accessDeniedHandler(myAccessDeniedHandler);

解决方式二

2、1:创建一个forbidden.html页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>权限不足,请联系管理员</h2>
</body>
</html>
2、2:修改配置页面
/** * 异常处理方式 */ 
http.exceptionHandling() 
	.accessDeniedPage("/forbidden.html");

十一、基于表达式的控制访问

1、access()的使用

之前学习的登陆用户权限判断实际上底层都是调用access(表达式),如permitAll()、anonymous()等。

public ExpressionInterceptUrlRegistry permitAll() {
   return access(permitAll);
}

public ExpressionInterceptUrlRegistry anonymous() {
   return access(anonymous);
}

那这个access(表达式)究竟是什么呢?
在这里插入图片描述
这是官方原文(原文地址:https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#authz-hierarchical-roles)
在这里插入图片描述

借助翻译工具翻译后,大概意思是:Spring Security 3.0引入了使用Spring EL表达式作为一种授权机制的能力,除了简单地使用以前见过的配置属性和访问决策投票人之外。基于表达式的访问控制建立在相同的体系结构上,但允许将复杂的布尔逻辑封装在单个表达式中。

表达式格式为:

在这里插入图片描述

也就是说我们可以基于access(表达式)的方式完成一些较为复杂的权限判断,我们以hasRole()和permitAll()为例:

在这里插入图片描述

2、使用自定义权限逻辑方法

虽然这里面已经包含了很多的表达式(方法)但是在实际项目中很有可能出现需要自己自定义逻辑的情况。比如:判断登录用户是否具有访问当前URL权限。

2、1:新建接口及实现类

自定义一个接口,接口中的方法名必须是hasPermission,否则Security无法识别,代码如下:

public interface MyService {
    /**
     * 判断当前登录用户是否具有访问当前url权限
     * @param request request对象
     * @param authentication 权限对象
     * @return
     */
    boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

实现类代码如下:

@Service
public class MyServiceImpl implements MyService {
    //截取字符串的开始位置
    private final Integer SUBSTRING_REQUEST_URI = 1;

    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        //我们只需要从request中获取uri即可
        String requestURI = request.getRequestURI();
        //字符串截取,不需要前面的/,所以是从1开始
        requestURI = requestURI.substring(SUBSTRING_REQUEST_URI,requestURI.length());

        // 获取当前登录的用户,判断是否为UserDetails的实现类
        Object principal = authentication.getPrincipal();
        if(principal instanceof UserDetails){
            User user = (User) principal;
            //从user对象中获取权限集合
            Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
            return authorities.contains(requestURI);
        }
        return false;
    }
}

修改配置类

/**
 * 自定义方法实现访问控制
 */
.antMatchers("/product").access("@myServiceImpl.hasPermission(request,authentication)")

十二、基于注解的访问控制

在Spring Security中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过@EnableGlobalMethodSecurity进行开启后使用。

如果设置的条件允许,程序正常执行。如果不允许会报500
在这里插入图片描述

这些注解可以写到Service接口或方法上上也可以写到Controller或Controller的方法上。通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。

1、@Secured

@Secured是专门用于判断是否具有角色的。能写在方法或类上。@Secured参数要以ROLE_开头。
在这里插入图片描述

1、1:实现步骤
1、1、1:开启注解

在启动类上添加@EnableGlobalMethodSecurity(securedEnabled = true)
在这里插入图片描述

1、1、2:在控制层方法上添加@Secured

在这里插入图片描述

1、1、3:配置类保留最基本配置即可
protected void configure(HttpSecurity http) throws Exception {
    // 表单认证
    http.formLogin()
            .loginProcessingUrl("/login")   //当发现/login时认为是登录,需要执行UserDetailsServiceImpl
            .successForwardUrl("/main")   //此处是post请求
            .loginPage("/login.html");

    // url 拦截
    http.authorizeRequests()
            .antMatchers("/login.html","/noauth.html").permitAll() //login.html不需要被认证
            .anyRequest().authenticated();//所有的请求都必须被认证。必须登录后才能访问。

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

}

2、@PreAuthorize/@PostAuthorize

@PreAuthorize和@PostAuthorize都是方法或类级别注解。

在这里插入图片描述

@PreAuthorize表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和access()方法参数取值相同,都是权限表达式。

@PostAuthorize表示方法或类执行结束后判断权限,此注解很少被使用到。

2、1:实现步骤
2、1、1:开启注解

在这里插入图片描述

2、1、2:添加注解到控制层方法上

在这里插入图片描述

2、1、3:与@Secured一样即可

十三、Remember Me功能实现

Spring Security 中Remember Me为“记住我”功能,用户只需要在登录时添加remember-me复选框,取值为true。Spring Security会自动把用户信息存储到数据源中,以后就可以不登录进行访问。

在上面我们已经添加了数据库的依赖以及配置连接信息,我们只需要做下面几步操作即可实现Remember Me功能

1、编写RememberMe配置文件

@Configuration
public class RememberMeConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository getTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);//将token信息保存到数据库中
        return jdbcTokenRepository;
    }
}

2、修改SpringSecurityConfig配置文件

在SecurityConfig中添加RememberMeConfig和UserDetailsService实现类对象,并自动注入。

在configure中添加下面配置内容。

/**
 * rememberMe处理方式
 */
http.rememberMe()
        .userDetailsService(myAuth) //自己编写的登陆认证类
        .tokenRepository(persistentTokenRepository);

3、在客户端中添加复选框

在客户端登录页面中添加remember-me的复选框,只要用户勾选了复选框下次就不需要进行登录了。

<form action="/login" method="post">
    用户名:<p><input type="text" name="username"/></p>
    密码:<p><input type="password" name="pwd"/></p>
    <input type="checkbox" name="remember-me" value="true"/> <br/>
    <p>记住我:<input type="submit" value="登录"/></p>
</form>

4、设置remember me的过去时间

默认2周时间。但是可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录。

/**
 * rememberMe处理方式
 */
http.rememberMe()
        .tokenValiditySeconds(120)//单位:秒
        .userDetailsService(myAuth) //自己编写的登陆认证类
        .tokenRepository(persistentTokenRepository);

十四、Thymeleaf中SpringSecurity的使用

Spring Security可以在一些视图技术中进行控制显示效果。例如:JSP或Thymeleaf。在非前后端分离且使用Spring Boot的项目中多使用Thymeleaf作为视图展示技术。

Thymeleaf对Spring Security的支持都放在thymeleaf-extras-springsecurityX中,所以需要在项目中添加此jar包的依赖和thymeleaf的依赖。

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <!--如果你的spring-web/spring-webmvc的是4.X.X版本的,推荐用thymeleaf-extras-springsecurity4的-->
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

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

在html页面中引入security命名空间

<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security" >

1、获取属性

页面获取security中的属性,一般从UsernamePasswordAuthenticationToken类中获取,通过sec:authentication=""获取getXXX的内容,同时包括父类getXXX的内容。

UsernamePasswordAuthenticationToken类中的getXXX方法

在这里插入图片描述

父类AbstractAuthenticationToken中的getXXX方法
在这里插入图片描述

1、1:实现步骤
1、1、1:新建index.html

在项目resources中新建templates文件夹,在templates中新建index.html页面

在index.html中编写下面内容,测试获取到的值

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    登录账号:<span sec:authentication="name">123</span><br/>
    登录账号:<span sec:authentication="principal.username">456</span><br/>
    凭证:<span sec:authentication="credentials">456</span><br/>
    权限和角色:<span sec:authentication="authorities">456</span><br/>
    客户端地址:<span sec:authentication="details.remoteAddress">456</span><br/>
    sessionId:<span sec:authentication="details.sessionId">456</span><br/>
</body>
</html>
1、1、2:编写控制器

thymeleaf页面需要控制转发,在控制器类中编写下面方法

@RequestMapping("/main") 
public String demo(){   
	return "index"; 
}

2、 权限判断

在html页面中可以使用sec:authorize=”表达式”进行权限控制,判断是否显示某些内容。表达式的内容和access(表达式)的用法相同。如果用户具有指定的权限,则显示对应的内容;如果表达式不成立,则不显示对应的元素。

2、1:不同权限的用户显示不同的按钮
2、1、1:设置用户角色和权限

设定用户具有admin,/insert,/delete权限ROLE_abc角色。

return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_abc,/insert,/delete"));
2、1、2:控制页面显示效果

在页面中根据用户权限和角色判断页面中显示的内容

通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button>
<br/>
通过角色判断:
<button sec:authorize="hasRole('abc')">新增</button>
<button sec:authorize="hasRole('abc')">删除</button>
<button sec:authorize="hasRole('abc')">修改</button>
<button sec:authorize="hasRole('abc')">查看</button>

十五、退出登陆

用户只需要向Spring Security项目中发送/logout退出请求即可。

<a href="/logout">退出登录</a>

为了实现更好的效果,通常添加退出的配置。默认的退出url为/logout,退出成功后跳转到/login?logout

在这里插入图片描述

如果不希望使用默认值,可以通过下面的方法进行配置类中修改即可。

http.logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login.html");

十六、SpringSecurity中CSRF

刚开始学习Spring Security时,配置类中存在这么一行代码:

/**
 * 关闭csrf保护
 */
http.csrf().disable();

如果没有这行代码导致用户无法被认证。这行代码的含义是:关闭csrf防护。

1、什么是CSRF

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。

跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。

下面三种都为http://localhost:8080/abc的跨域请求

https://localhost:8080/abc

http://127.0.0.2:8080/abc

http://localhost:8081/abc

客户端与服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。

2、Spring Security中的CSRF

从Spring Security4开始CSRF防护默认开启。默认会拦截请求。进行CSRF处理。CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。

2、1:实现步骤
2、1、1:编写控制方法

编写控制器方法,跳转到templates中login.html页面。

注意:需要在SpringSecurityConfig中添加/showLogin的认证逻辑以及权限设置

@GetMapping("/showLogin")
public String showLogin() {
    return "login";
}

2、1、2:修改login.html页面

修改已有的long.html页面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action = "/login" method="post">
	<!--接收_csrf值,由服务端产生-->
    <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>
2、1、3:修改配置类

在配置类中注释掉CSRF防护失效

//        http.csrf().disable();
2、2:开启CSRF后的注销

如果我们开启了CSRF保护机制,则默认情况下,不能使用get方式的/logout

官方的说明(原文地址:https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#servlet-csrf-considerations):

在这里插入图片描述

翻译一下就是最简单的方式就是使用form表单退出,如果非要使用一个链接退出,可以通过JavaScript模拟一个表单,设置post退出。如果非要使用get方式退出,上图中官方也给出了代码

我们可以这么解决:

<form style="float: right" action="/logout" method="post">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <button type='submit'>退出</button>
</form>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值