首先准备三个不同权限的接口
@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必须在用户登录后才能进行操作。
/**
* @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/是允许所有角色访问。
到目前为止,我们仍然只有一个可登录的用户,怎样引入多用户呢?非常简单,我们只需实现一
个自定义的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接口中的一个实现类,它将用户数据源寄存在内存里,在一些不需要引入数据库这种重数据源的系统中很有帮助。
除了IMemoryUserDetailsManagsr, Spring Security还提供另一个UserDetailsService实现类:
JdbcUserDetailsManager。
JdbcUserDetailsManager帮助我们以JDBC的方式对接数据库和Spring Security,它设定了一个默认的数据库模型,只要遵从这个模型,在简便性上,JdbcUserDetailsManager甚至可以媲美InMemory UserDetailsManager。
数据库方面的不需要我多说什么了吧。
唯一要说的就是创建的表
JdbcUserDetailsManager需要两个表,其中users表用来存放用户名、密码和是否可用三个信息, authorities表用来存放用户名及其权限的对应关系。将其复制到MySQL命令窗口执行时,会报错,因为该语句是用hsqldb创建的,而MySQL不支持 varchar_ignorecase这种类型。怎么办呢?很简单,将varchar_ignorecase改为MySQL支持的varchar即可。
JdbcUserDetailsManager与InMemoryUserDetailsManager在用法上没有太大区别,只是多了设置 DataSource 的环节。Spring Security 通过 DataSource 执行设定好的命令。
例如,此处的createUser函数 实际上就是执行了下面的SQL语句。
查看 JdbcUserDetailsManager 的源代码可以看到更多定义好的 SQL 语句,诸如deleteUserSql、 updateUserSql等,这些都是JdbcUserDetailsManager与数据库实际交互的形式。当然, JdbcUserDetailsManager 也允许我们在特殊情况下自定义这些 SQL语句,如有必要,调用对应的setXxxSql方法即可。
当使用Spring Security默认数据库模型应对各种用户系统时,难免灵活性欠佳。尤其是在对现有的 系统做Spring Security嵌入时,原本的用户数据已经固定,为了适配Spring Security而在数据库层面进行 修改显然得不偿失。强大而灵活的Spring Security对这方面进行了改进。
之前使用了InMemoryUserDetailsManager 和 JdbcUserDetailsManager 两个UserDetailsService 实现类。生效方式也很简单,只需加入 Spring 的 IoC 容器,就会被 Spring Security自动发现并使用。自定义数据库结构实际上也仅需实现一个自定义的UserDetails Service。
UserDetailsService仅定义了一个loadUserBy Username方法,用于获取一个UserDetails对象。UserDetails对象包含了一系列在验证时会用到的信息,包括用户名、密码、权限以及其他信息,Spring Security会根据这些信息判定验证是否成功。
也就是说,不管数据库结构如何变化,只要能构造一个UserDetails即可,下面就来实现这个过 程。
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字段,但由于结构不一致,所以此处新建一个,并在后续 进行填充。
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();
}
}
登录数据库保存的账号密码用户名:user密码:1权限role_user
返回配置的成功页面
然后依次访问不同权限的接口
登录数据库保存的账号密码用户名: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协议进行许可。转载请署名作者且注明文章出处。