Spring Security认证和授权(二)

1. 默认数据库认证和授权

1.1 资源准备

首先准备三个不同权限的接口

@GetMapping("/admin/test")
@ResponseBody
public String adminTest() {
    return "admin test";
}

@GetMapping("/user/test")
@ResponseBody
public String userTest() {
    return "user test";
}

@GetMapping("/web/test")
@ResponseBody
public String webTest() {
    return "web test";
}

假设在/admin/test/下的内容是系统后台管理相关的 API,在/web/test下的内容是面向客户端公开访 问的API,在/user/test/下的内容是用户操作自身数据相关的API;显然,/admin/test必须拥有管理员权限才能进行操作,而/user/test必须在用户登录后才能进行操作。

1.2 资源授权的配置

/**
 * @author: Java技术债务
 * @Date: 2021/6/5 18:45
 * Describe: SpringSecurity配置
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/test/**").hasRole("admin")
                .antMatchers("/user/test/**").hasRole("user")
                .antMatchers("/web/test/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();

    }
}

antMatchers()是一个采用ANT模式的URL匹配器。ANT模式使用?匹配任意单个字符,使用* 匹配0或任意数量的字符,使用匹配0或者更多的目录。antMatchers("/admin/test/")相当于匹配 了/admin/test/下的所有API。此处我们指定当其必须为admin角色时才能访问,/user/test/与之同 理。/web/test/下的API会调用permitAll()公开其权限。

重启服务测试

在这里插入图片描述

在这里插入图片描述

页面显示403错误,表示该用户授权失败(401代表该用户认证失败)。也就是说,本次访问已经通过了认证环节,只是在授权的时候被驳回了。认证环节是没有问题的,因为Spring Security默认的用 户角色正是user。
HTTP状态码(HTTP Status Code)是由RFC 2616定义的一种用来表示一个HTTP请求响应状态的 规范,由3位数字组成。通常用2XX表示本次操作成功,用4XX表示是客户端导致的失败,用5XX表示 是服务器引起的错误。

访问/web/test/却成功了,因为/web/test/是允许所有角色访问。

1.3 基于内存的多用户支持

到目前为止,我们仍然只有一个可登录的用户,怎样引入多用户呢?非常简单,我们只需实现一
个自定义的UserDetails Service即可。

package com.demo.springSecurityFirstDemo.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author: Java技术债务
 * @Date: 2021/6/5 18:45
 * Describe: SpringSecurity配置
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/test/**").hasRole("ADMIN")
                .antMatchers("/user/test/**").hasRole("USER")
                .antMatchers("/web/test/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();

    }

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(User.withUsername("user").password("1").roles("USER").build());
        userDetailsManager.createUser(User.withUsername("admin").password("1").roles("USER", "ADMIN").build());

        return userDetailsManager;
    }
}

为其添加一个@bean注解,便可被 Spring Security 发现并使用。Spring Security支持各种来源的用户数据,包括内存、数据库、LDAP等。它们被抽象为一个UserDetailsService接口,任何实现了
UserDetailsService 接口的对象都可以作为认证数据源。在这种设计模式下,Spring Security 显得尤为灵活。
IMemory UserDetailsManager是 UserDetails Service接口中的一个实现类,它将用户数据源寄存在内存里,在一些不需要引入数据库这种重数据源的系统中很有帮助。

1.4 认证和授权

除了IMemoryUserDetailsManagsr, Spring Security还提供另一个UserDetailsService实现类:
JdbcUserDetailsManager。

JdbcUserDetailsManager帮助我们以JDBC的方式对接数据库和Spring Security,它设定了一个默认的数据库模型,只要遵从这个模型,在简便性上,JdbcUserDetailsManager甚至可以媲美InMemory UserDetailsManager。

1.4.1 数据库准备

数据库方面的不需要我多说什么了吧。

唯一要说的就是创建的表

在这里插入图片描述

JdbcUserDetailsManager需要两个表,其中users表用来存放用户名、密码和是否可用三个信息, authorities表用来存放用户名及其权限的对应关系。将其复制到MySQL命令窗口执行时,会报错,因为该语句是用hsqldb创建的,而MySQL不支持 varchar_ignorecase这种类型。怎么办呢?很简单,将varchar_ignorecase改为MySQL支持的varchar即可。

1.4.2 编码

在这里插入图片描述

JdbcUserDetailsManager与InMemoryUserDetailsManager在用法上没有太大区别,只是多了设置 DataSource 的环节。Spring Security 通过 DataSource 执行设定好的命令。

例如,此处的createUser函数 实际上就是执行了下面的SQL语句。

查看 JdbcUserDetailsManager 的源代码可以看到更多定义好的 SQL 语句,诸如deleteUserSql、 updateUserSql等,这些都是JdbcUserDetailsManager与数据库实际交互的形式。当然, JdbcUserDetailsManager 也允许我们在特殊情况下自定义这些 SQL语句,如有必要,调用对应的setXxxSql方法即可。

在这里插入图片描述

当使用Spring Security默认数据库模型应对各种用户系统时,难免灵活性欠佳。尤其是在对现有的 系统做Spring Security嵌入时,原本的用户数据已经固定,为了适配Spring Security而在数据库层面进行 修改显然得不偿失。强大而灵活的Spring Security对这方面进行了改进。

2. 自定义数据库模型的认证与授权

2.1 实现UserDetails

之前使用了InMemoryUserDetailsManager 和 JdbcUserDetailsManager 两个UserDetailsService 实现类。生效方式也很简单,只需加入 Spring 的 IoC 容器,就会被 Spring Security自动发现并使用。自定义数据库结构实际上也仅需实现一个自定义的UserDetails Service。

UserDetailsService仅定义了一个loadUserBy Username方法,用于获取一个UserDetails对象。UserDetails对象包含了一系列在验证时会用到的信息,包括用户名、密码、权限以及其他信息,Spring Security会根据这些信息判定验证是否成功。

在这里插入图片描述

也就是说,不管数据库结构如何变化,只要能构造一个UserDetails即可,下面就来实现这个过 程。

2.2 数据库以及表准备

SecurityUser.java

package com.demo.springSecurityFirstDemo.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * @Author Java技术债务
 * @Date 2022-03-26 19:12:56
 * @Desc *
 */
public class SecurityUser implements UserDetails {
    private Integer id;

    private String username;

    private String password;

    private boolean enable;

    private List<GrantedAuthority> authorityList;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
    }

    public List<GrantedAuthority> getAuthorityList() {
        return authorityList;
    }

    public void setAuthorityList(List<GrantedAuthority> authorityList) {
        this.authorityList = authorityList;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorityList;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enable;
    }
}

实现UserDetails定义的几个方法:
◎ isAccountNonExpired、isAccountNonLocked 和 isCredentialsNonExpired 暂且用不到,统一返回 true,否则Spring Security会认为账号异常。
◎ isEnabled对应enable字段,将其代入即可。
◎ getAuthorities方法本身对应的是roles字段,但由于结构不一致,所以此处新建一个,并在后续 进行填充。

2.3 实现UserDetailsService

package com.demo.springSecurityFirstDemo.security;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.demo.springSecurityFirstDemo.model.UserModel;
import com.demo.springSecurityFirstDemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author Java技术债务
 * @Date 2022-03-22 22:19:51
 * @Desc *
 *
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;
    /**
     * Security的登录,User赋予权限
     *
     * @param userName
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        System.out.println(userName);
        QueryWrapper<UserModel> qw = new QueryWrapper<>();
        qw.eq("user_name", userName);
        UserModel userModel = userService.getOne(qw);
        if (userModel == null) {
            throw new UsernameNotFoundException("user not exist!");
        }
        SecurityUser securityUser = new SecurityUser();
        securityUser.setId(userModel.getId());
        securityUser.setUsername(userModel.getUserName());
        securityUser.setPassword(userModel.getPassword());
        securityUser.setAuthorityList(AuthorityUtils.commaSeparatedStringToAuthorityList(userModel.getRoles()));
        securityUser.setEnable(userModel.getIsDeleted() == 0);

        return securityUser;
    }
}

其中AuthorityUtils.commaSeparatedStringToAuthorityList(userModel.getRoles()) 是将逗号拼接的权限字符串,比如:ROLE_USER,ROLE_ADMIN。

SimpleGrantedAuthority是GrantedAuthority的一个实现类。Spring Security的权限几乎是用 SimpleGrantedAuthority生成的,只要注意每种角色对应一个GrantedAuthority即可。另外,一定要在自 己的UserDetailsService实现类上加入@Service注解,以便被Spring Security自动发现。

[WebSecurityConfig.java](http://WebSecurityConfig.java) 完整代码

package com.demo.springSecurityFirstDemo.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
 * @author: Java技术债务
 * @Date: 2021/6/5 18:45
 * Describe: SpringSecurity配置
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/test/**").hasRole("ADMIN")
                .antMatchers("/user/test/**").hasRole("USER")
                .antMatchers("/web/test/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();

    }
}

2.4 启动程序测试

登录数据库保存的账号密码用户名:user密码:1权限role_user

返回配置的成功页面

在这里插入图片描述

然后依次访问不同权限的接口

  • 访问/admin/test

在这里插入图片描述

  • 访问/user/test

在这里插入图片描述

登录数据库保存的账号密码用户名:admin密码:1权限为role_admin和role_user

访问/admin/test和/user/test/正常。

如果登录不存在的用户测试结果如图。

在这里插入图片描述

在这里插入图片描述
JVM内存泄漏和内存溢出的原因
JVM常用监控工具解释以及使用
Redis 常见面试题(一)
ClickHouse之MaterializeMySQL引擎(十)
三种实现分布式锁的实现与区别
线程池的理解以及使用

号外!号外!

最近面试BAT,整理一份面试资料,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。想获取吗?如果你想提升自己,并且想和优秀的人一起进步,感兴趣的朋友,可以在扫码关注下方公众号。资料在公众号里静静的躺着呢。。。

  • 喜欢就收藏
  • 认同就点赞
  • 支持就关注
  • 疑问就评论

一键四连,你的offer也四连

————————————————————————————————————————————————————————————————

本文作者:Java技术债务
原文链接:https://www.cuizb.top/myblog/article/1648393808
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。

posted @ 2022-06-30 20:52  Java技术债务  阅读(72)  评论(0编辑  收藏  举报