SpringSecurity 应用
简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
认证授权分析
用户在进行资源访问时,要求系统要对用户进行权限控制,其具体流程如图-1所示:
SpringSecurity 架构设计
鸟瞰SpringSecurity 基本技术架构,例如:
绿色部分是认证过滤器,需要我们自己配置,可以配置多个认证过滤器。认证过滤器可以使用 Spring Security 提供的认证过滤器,也可以自定义过滤器(例如:短信验证)。认证过滤器要在 configure(HttpSecurity http)方法中配置,没有配置不生效。下面会重点介绍以下三个过滤器:
UsernamePasswordAuthenticationFilter 过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证。
BasicAuthenticationFilter:检测和处理 http basic 认证。
ExceptionTranslationFilter 过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)。
FilterSecurityInterceptor 过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并由 ExceptionTranslationFilter 过滤器进行捕获和处理。
快速入门实践
创建项目
创建security项目,其pom.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.2.RELEASE</version>
</parent>
<groupId>com.cy</groupId>
<artifactId>05-security</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
添加项目依赖
第一步:创建项目,其pom.xml文件核心依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
第二步:启动服务(依赖添加以后会默认添加一个tomcat,端口8080) 服务启动之后,你会发现,控制台会出现一个随机的密码,用于访问当前系统,默认用户名是user,密码就是控制台上的密码,如图所示:
启动服务访问测试
服务启动后,打开浏览器进行访问,如图所示:
输入账号(默认用户名为user)和密码登陆成功默认为如下页面.
其中,出现这个页面表示还没有配置登陆成功页面,这个资源页面现在还不存在,可以在项目的resources目录下创建static目录(假如没有此目录),然后在此目录下创建index.html页面,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<h1>The Index Page</h1>
</div>
</body>
</html>
此时,再次启动服务进行登陆,呈现登陆成功的效果,如图所示:
自定义认证逻辑
认证流程分析
定义security配置类
定义配置类,基于此类配置认证和授权逻辑,例如:
package com.cy.security.config;
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.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");
http.authorizeRequests()
.antMatchers("/login","/login.html")
.permitAll()
.anyRequest().authenticated();
}
}
定义数据访问层对象
定义数据访问层对象,基于此对象实现用户及用户权限信息的获取,例如:
package com.cy.security.dao;
import com.cy.security.domain.SysUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
@Select("select id,username,password,status " +
"from tb_users " +
"where username=#{username}")
SysUser selectUserByUsername(String username);
@Select("select distinct m.permission " +
"from tb_user_roles ur join tb_role_menus rm on ur.role_id=rm.role_id" +
" join tb_menus m on rm.menu_id=m.id " +
"where ur.user_id=#{userId} and m.permission is not null")
List<String> selectUserPermissions(Long userId);
}
定义UserDetailService接口实现类
Spring Security 提供了一个UserDetailService接口,我们可以基于此接口实现类,实现用户信息的获取和封装,例如:
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user=userMapper.selectUserByUsername(username);
System.out.println(user);
List<String> permissions=userMapper.selectUserPermissions(user.getId());
System.out.println(permissions);
List<GrantedAuthority> authorityList =
AuthorityUtils.createAuthorityList(permissions.toArray(new String[]{}));
return new User(username, user.getPassword(),authorityList);
}
}
自定义登陆页面
在resources的static目录下创建login.html页面,例如:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>login</title>
</head>
<body>
<div class="container"id="app">
<h3>Please Login</h3>
<form>
<div class="mb-3">
<label for="usernameId" class="form-label">Username</label>
<input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
</div>
<div class="mb-3">
<label for="passwordId" class="form-label">Password</label>
<input type="password" v-model="password" class="form-control" id="passwordId">
</div>
<button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var vm=new Vue({
el:"#app",
data:{
username:"",
password:""
},
methods: {
doLogin() {
let url = "http://localhost:8080/login"
let params = new URLSearchParams()
params.append('username',this.username);
params.append('password',this.password);
debugger
axios.post(url, params).then((response) => {
alert("login ok");
location.href="/index.html"
})
}
}
});
</script>
</body>
</html>
启动服务进行访问测试
授权逻辑设计及实现
修改授权配置类
在权限配置类上添加启用全局方法访问控制注解,例如
package com.cy.auth.config;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter {
……
}
定义资源访问对象
package com.cy.res.controller;
@RequestMapping("/res")
@RestController
public class ResourceController {
@PreAuthorize("hasAuthority('sys:res:view')")
@RequestMapping("/retrieve")
public String doRetrieve(){
return "select resource ok";
}
@PreAuthorize("hasAuthority('sys:res:create')")
@RequestMapping("/create")
public String doCreate(){
return "create resource";
}
}
其中,@PreAuthorize注解描述方法时,用于告诉系统访问此方法时需要进行权限检测。需要具备指定权限才可以访问。例如:
启动服务实现访问测试
打开浏览器分别输入http://localhost:8080/res/create和http://localhost:8080/res/select进行测试分析。
总结(Summary)
本章节主要是对spring security 在springboot平台下是如何使用的。
|