Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC、DI和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring Security 是一个当今非常流行的安全框架。
环境
Spring Boot2.3.0.RELEASE
Spring Security5.3.2
IntelliJ IDEA 2020.1 x64
内存用户认证与授权
首先创建一个 SpringBoot 项目,引入 Spring Security 的依赖,在没有写 Spring Security 的相关配置时,启动项目。访问自己写的一个页面,会发现也是需要进行登录的,虽然自己并没有用户密码的验证,这时 Spring Security 默认提供了一个 user 账号,密码在启动时随机生成,登录之后才能访问到自己写的页面。
1)引入 Spring Security 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2)写一些 html 页面(如:主页、自定义登录页、其他一些功能页)
3)Controller 处理请求
@Controller
public class MainController {
@RequestMapping("/index")
public String index(){
return "index";
}
@RequestMapping("/login")
public String mainPage(){
System.out.println("/login....");
return "normal/success";
}
@RequestMapping("/fail")
public String failPage(){
return "fail";
}
@RequestMapping("/add")
public String addPage(){
return "normal/resadd";
}
....
}
4)配置认证与授权规则
@Configuration
@EnableWebSecurity
public class SpringsecurityConfig extends WebSecurityConfigurerAdapter {
//@Autowired
//private SysUserService userService;
//配置加密
//@Bean //这里注释掉不启用加密
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); //spring security 内置加密算法
}
//认证用户的来源
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
//下面定义了两个内存用户
auth.inMemoryAuthentication().withUser("xiao")
.password("{noop}123") //noop 表示没有加密
.roles("NORMAL"); //用户xiao有NORMAL角色
auth.inMemoryAuthentication().withUser("admin")
.password("{noop}123")
.roles("NORMAL") //用户admin有NORMAL角色
.authorities("add","del"); //用户admin有"add"和"del"的权限
}
//Spring Security配置
public void configure(HttpSecurity hs) throws Exception {
hs.authorizeRequests()
.antMatchers("/index","/css/**").permitAll() //允许所有请求访问
.antMatchers("/add","/del").hasAnyAuthority("add","del") // /add 和/del 需要 add或del的权限
.antMatchers("/**").hasAnyRole("NORMAL") //所有资源都要 NORMAL 这个角色才能访问
.anyRequest().authenticated() //其他请求只需要认证即可访问
.and() //开始新的配置
.formLogin() //以表单形式登录
.loginPage("/index") //使用自定义登录页面
.loginProcessingUrl("/login")
.successForwardUrl("/login") //登录成功跳转的页面
.failureForwardUrl("/fail") //登录失败跳转页面,其实可以不写,默认回到登录页
.and()
.csrf()
.disable(); //不控制跨站攻击,Security 默认对跨站攻击做了安全控制
}
}
5)测试
启动项目,直接访问 /login、/add 等页面是会默认跳转回 /index 到登录页面的
使用 xiao/123 或 admin/123 登录成功,可以访问有权访问的页面;否则到了 fail.html 页面
这个例子中,我发现登录之后,访问了一个功能页,发现点网页的后退
<–
,会出错,提示 “确认重新提交表单”,没弄明白是哪里出问题了。如果有朋友知道,希望留言指点一下,先谢谢了
自定义错误页面
当上面的例子中用 xiao 账号登录时,想访问增加的页面,但是没有权限,会看到一个默认提共的错误页面,对用户很不友好。所以应该自定义一个普通用户能看懂的页面
写一个配置类
@Configuration
public class ErrorPageConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
ErrorPage errorPage = new ErrorPage(HttpStatus.FORBIDDEN, "/403");
registry.addErrorPages(errorPage);
}
}
然后当然需要在 controller 类中加入对应的请求
@RequestMapping("/403") //对应 ErrorPageConfig中的路径
public String forbidden() {
return "403";
}
这样,当用户访问没有权限的页面时就能看到我们自定义的错误页面了。当然,还可以定义其他的错误页面,具体看看 HttpStatus 类中的状态码。
数据库用户认证与授权
准备数据库(本文用到mysql8.0.20)
本文配置了所有资源都要 NORMAL 角色才能访问,数据库的角色名要写成为 ROLE_NORMAL,否则角色名不正确,登录成功了也无法访问其他功能
1)加入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2)加入相关配置
## 配置数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo_database?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=yourpwd
## mybatis配置
mybatis.type-aliases-package=com.xiao.springsecurity.entity
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.xiao.springsecurity=debug
3)编写实体类
需要实现相应的 Spring Security 接口,记得把重写方法的布尔值都改为 true
public class SysUser implements UserDetails {
private Integer id;
private String username;
private String password;
private List<SysRole> roles;
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;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
.....
}
public class SysRole implements GrantedAuthority {
private Integer id;
private String roleName;
private String roleDesc; //角色描述
@JsonIgnore //该注解分布式实现需要
@Override
public String getAuthority() {
return roleName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
.....
}
4)编写mapper
public interface SysUserMapper extends Mapper {
@Select("select * from sys_user where username = #{username}")
@Results({
@Result(id=true, property = "id", column = "id"),
@Result(property = "roles", column = "id", javaType = List.class,
many = @Many(select = "com.xiao.springsecurity.mapper.SysRoleMapper.findRolesByUid"))
})
SysUser findUserByName(String username);
}
角色表字段与角色类属性不一致,使用别名
public interface SysRoleMapper extends Mapper {
@Select("select sr.id, sr.role_name roleName, sr.role_desc roleDesc " +
"from sys_user_role sur, sys_role sr " +
"where sur.rid=sr.id and sur.uid = #{uid}")
List<SysRole> findRolesByUid(Integer uid);
}
5)编写service
接口继承 UserDetailsService ,要不然 Spring Security 无法识别
public interface SysUserService extends UserDetailsService {
}
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired(required = false)
private SysUserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("123密文:" + passwordEncoder.encode("123"));
SysUser sysUser = userMapper.findUserByName(username);
System.out.println(sysUser.getUsername() + "====" + sysUser.getPassword());
List<SysRole> roles= (List<SysRole>) sysUser.getAuthorities();
for (int i = 0; i < roles.size(); i++) {
System.out.println("数据库角色名称:" + roles.get(i).getRoleName());
}
/* 用下面这个返回 user 也可以,不过数据不是从数据库取的了
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("NORMAL"));
User user = new User(username,passwordEncoder.encode("123"),authorities);
*/
return sysUser;
}
}
6)修改 Spring Security 配置类
@Configuration
@EnableWebSecurity
public class SpringsecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SysUserService userService;
//配置加密
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); //spring security 内置加密算法
}
//认证用户的来源
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
/*
auth.inMemoryAuthentication().withUser("xiao")
.password("{noop}123") //noop 表示没有加密
.roles("NORMAL");
auth.inMemoryAuthentication().withUser("admin")
.password("{noop}123")
.roles("NORMAL")
.authorities("add","del");
*/
}
//Spring Security配置
public void configure(HttpSecurity hs) throws Exception {
hs.authorizeRequests()
.antMatchers("/index","/css/**").permitAll()
//.antMatchers("/add","/del").hasAnyAuthority("add","del")
.antMatchers("/**").hasAnyRole("NORMAL")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/index")
.loginProcessingUrl("/login")
.successForwardUrl("/login")
.failureForwardUrl("/fail")
.and()
.csrf()
.disable();
}
}
7)测试
记得在启动类加上 Mapper 扫描注解
@SpringBootApplication
@MapperScan("com.xiao.springsecurity.mapper")
public class SpringsecurityApplication {
...
…