六:Day06_Spring Security02

发布时间:2023年12月17日

一、访问控制(授权)

1. 基于资源访问控制

  • 查询用户的权限。

  • 访问资源时判断用户是否具有指定的权限。

1.1?修改UserServiceImpl
@Service
public class UserServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private MenuMapper menuMapper;

    /**
     * @param username  前端登录时提交的用户名
     * @return          用户的认证信息,要求必须为UserDetails接口的实现类
     * @throws UsernameNotFoundException  用户名没有找到时的异常
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        User myUser = userMapper.selectOne(wrapper);
        if (myUser == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        
        //查询用户拥有的权限
        List<Menu> menus = menuMapper.selectByUserId(myUser.getId());
        ArrayList<GrantedAuthority> list = new ArrayList<>();
        menus.forEach(menu -> {
            //获取的权限为null时,将null添加到SimpleGrantedAuthority会出现
            //A granted authority textual representation is required异常信息
            String permission = menu.getPermission();
            if (permission != null && permission!=""){
                list.add(new SimpleGrantedAuthority(permission));
            }
        });

        //参数一:用户名  参数二:密码  参数三:权限
        /*
        * org.springframework.security.core.userdetails.User为UserDetails接口的实现类。
        * 也可以自定义的User实现UserDetail接口提供对应的属性。
        * */
        org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(
                myUser.getUsername(), myUser.getPassword(), list);
        return user;
    }
}
1.2?修改配置类
	//请求授权设置。
        /*
        * antMatchers():对指定的请求url进行控制
        * permitAll():允许访问
        * anyRequest():任意一个请求url的控制
        * authenticated():认证后便可以访问
        * hasAuthority():存在指定的权限可以访问
        * hasAnyRole(String ...):存在指定的任意一个权限可以访问
        * */
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll()  //login.html不需要被认证
                .antMatchers("/stu/select").hasAuthority("stu:select") //需要有指定的权限
                .antMatchers("/stu/insert").hasAuthority("stu:insert") //需要有指定的权限
                .antMatchers("/stu/update").hasAuthority("stu:update") //需要有指定的权限
                .antMatchers("/stu/del").hasAuthority("stu:del")       //需要有指定的权限
                .anyRequest().authenticated();                       //其它任意的请求都必须被认证,必须认证(登录成功)后就可以直接访问

2. 基于角色访问控制

2.1 修改UserServiceImpl

官网要求:角色前必须添加 ROLE_ 前缀,角色才会生效。例如:ROLE_admin。

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private RoleMapper roleMapper;

    /**
     * @param username 前端登录时提交的用户名
     * @return 用户的认证信息,要求必须为UserDetails接口的实现类
     * @throws UsernameNotFoundException 用户名没有找到时的异常
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        User myUser = userMapper.selectOne(wrapper);
        if (myUser == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        /*
         * org.springframework.security.core.userdetails.User为UserDetails接口的实现类。
         * 也可以自定义的User实现UserDetail接口提供对应的属性。
         * */
        //查询用户拥有的权限
        List<Menu> menus = menuMapper.selectByUserId(myUser.getId());
        ArrayList<GrantedAuthority> list = new ArrayList<>();
        menus.forEach(menu -> {
            //获取的权限为null时,将null添加到SimpleGrantedAuthority会出现
            //A granted authority textual representation is required异常信息
            String permission = menu.getPermission();
            if (permission != null && permission != "") {
                list.add(new SimpleGrantedAuthority(permission));
            }
        });
        
        //查询用户所有角色
        List<Role> roles = roleMapper.selectByUserId(myUser.getId());
        roles.forEach(role -> {
            list.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        });

        //参数一:用户名  参数二:密码  参数三:权限
        org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(
                myUser.getUsername(), myUser.getPassword(), list);
        return user;
    }
}
2.2 修改配置类
 //请求授权设置。
        /*
        * antMatchers():对指定的请求url进行控制
        * permitAll():允许访问
        * anyRequest():任意一个请求url的控制
        * authenticated():认证后便可以访问
        * hasAuthority():存在指定的权限可以访问
        * hasAnyRole(String ...):存在指定的任意一个权限可以访问
        * hasRole():存在指定的角色可以访问
        * hasAnyRole():存在指定的任意一个角色可以访问
        * */
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll()  //login.html不需要被认证
                .antMatchers("/stu/select").hasAuthority("stu:select") //需要有指定的权限
                .antMatchers("/stu/insert").hasAuthority("stu:insert") //需要有指定的权限
                .antMatchers("/stu/update").hasRole("管理员")          //需要有指定的角色
                .antMatchers("/stu/del").hasRole("管理员")             //需要有指定的角色
                .anyRequest().authenticated();                        //其它任意的请求都必须被认证,必须认证(登录成功)后就可以直接访问

二、基于注解的访问控制

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

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

1. 注解介绍

1.1 @PreAuthorize
  • @PreAuthorize:表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解。

  • 注意:必须在启动类@EnableGlobalMethodSecurity中设置prePostEnabled = true

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

2 . 修改配置类

  • 配置类中不再需要配置基于资源和基于角色的权限控制。

3. 修改启动器

  • 在启动类中通过@EnableGlobalMethodSecurity开启@PreAuthorize注解。

  • @EnableGlobalMethodSecurity(prePostEnabled = true)

4. 修改控制器

  • 在控制器方法上添加@PreAuthorize。

@RestController
@RequestMapping("stu")
public class StudentController {

    @RequestMapping("insert")
    @PreAuthorize("hasAuthority('stu:select')")
    public String insert(){
        return "添加学生";
    }

    @RequestMapping("update")
    @PreAuthorize("hasAuthority('stu:update')")
    public String update(){
        return "修改学生";
    }

    @RequestMapping("del")
    @PreAuthorize("hasRole('ROLE_管理员')")
    public String del(){
        return "删除学生";
    }

    @RequestMapping("select")
    @PreAuthorize("hasRole('ROLE_管理员')")
    public String select(){
        return "查询学生";
    }
}

三、自定义403处理方案

在Spring Security 的 自定义配置类( WebSecurityConfigurerAdapter )中使用HttpSecurity 提供的 exceptionHandling() 方法用来处理异常。该方法构造出 ExceptionHandlingConfigurer 异常处理配置类。该配置类提供了两个实用接口:

  1. AuthenticationEntryPoint 该类用来统一处理 AuthenticationException 异常

  2. AccessDeniedHandler 该类用来统一处理 AccessDeniedException 异常

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();
     }
}

2. 修改配置类

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

@Autowired
private AccessDeniedHandler accessDeniedHandler;
//异常处理
http.exceptionHandling()
        .accessDeniedHandler(accessDeniedHandler);

四、Spring Security整合Thymeleaf使用

在项目中添加此jar包的依赖和thymeleaf的依赖。

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <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添加命名空间。

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

1. 获取属性

根据源码得出下面属性:

  1. name:登录账号名称

  2. principal:登录主体,在自定义登录逻辑中是UserDetails

  3. credentials:凭证

  4. authorities:权限和角色

  5. details:实际上是WebAuthenticationDetails的实例。可以获取remoteAddress(客户端ip)和sessionId(当前sessionId)

<!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>

2. 权限判断

如果用户具有指定的权限,则显示对应的内容;如果表达式不成立,则不显示对应的元素。

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

五、Spring Security中CSRF

1. CSRF

CSRF英文全称叫做: cross-site request forgery,翻译过来叫做跨站请求伪造。spring security默认情况下是开启了csrf保护的。

CSRF 是致击者通过一些技术手段欺骗用户的浏览器,去访问一个用户曾经认证过的网站并执行恶意请求,例如发送邮件、发消息、甚至财产操作(如转账和购买商品)。由于客户端(浏览器)已经在该网站中认证过了,所以该网站会认为是真正用户在操作而执行请求(实际上并非用户的本意)。

2. Spring Security中CSRF

从Spring Security4开始CSRF防护默认开启。默认会拦截请求,以防止CSRF攻击应用程序处理。默认情况下会启用 CSRF 保护,,SpringSecurity CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。

注意,这里面不包括GET、HEAD、TRACE、OPTIONS请求,这些请求还是会存在这种问题的。

3. Spring Security中CSRF原理

  1. 当服务器加载登录页面,先生成csrf对象,并放入作用域中,key为_csrf。

  2. 用户在提交登录表单时,会携带csrf的token。如果客户端的token和服务器的token匹配说明是自己的客户端,否则无法继续执行。

  3. 用户退出的时候,必须发起POST请求,且和登录时一样,携带csrf的令牌。

4. 实现??

4.1 login.html

在项目resources下新建templates文件夹,并在文件夹中新建login.html页面。form表单中的第一行是必须存在的否则无法正常登录。

<!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">
	<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>
4.2 修改配置类

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

文章来源:https://blog.csdn.net/qq_51318621/article/details/134898361
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。