Spring Security

Spring Security 核心部门

  • 认证:登录、用户认证
  • 授权:对资源的访问控制、基于角色实现
  • 安全:内置了实现主流的加密算法
  • 会话管理:多个请求响应之间的业务流程切换【基于cookie、session】

认证与授权的联系和区别

认证是识别用户身份是否合法,授权是给已经通过认证的用户访问权限,授权是发生在认证之后的。

认证

认证的4种方式

  • 默认设置
    • 用户名:默认 user
    • 密码:控制台输出的一串安全密码
  • 基于配置文件中的用户认证设置
    • 在配置文件中设置一个用户名和密码
  • 基于内存的认证
    • 用户名和密码以编码方式写在程序中,加载配置文件,属于硬编码,写死了
  • 基于 JDBC 的认证
    • 定义至少两张表

接下来体验一下 4 种认证设置方式
首先 Spring 新建工程,勾选 Spring Boot DevTools 、Spring Security、Spring Web
在这里插入图片描述

2、新建一个 SecurityConfig 配置类,继承 WebSecurityConfigurerAdapter,重写两个其中两个方法,一个用于用户认证管理、一个用于用户授权管理,为了了解用户认证设置,这里先重点关注第一个方法
① 默认设置
直接运行项目,会自动生成一串安全密码,用户名默认是user,随后用于访问浏览器时登录

public class SecurityConfig extends 

WebSecurityConfigurerAdapter{
	
	// 用户认证管理
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// TODO Auto-generated method stub
		super.configure(auth);
	}

	// 用户授权管理
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// TODO Auto-generated method stub
		super.configure(http);
	}
}

查看控制台输出,自动生成的安全密码:
默认生成的密码
②基于配置文件中的用户认证
在配置文件中写入下面两句配置信息

# 自定义
spring.security.user.name=test
spring.security.user.password=123456

随后注释掉用户认证方法里的这一句话,就会启用配置文件中自定义的

// super.configure(auth);

③基于内存的认证
用户名和密码写在程序中,运行时加载,注意密码需要加密处理,否则会出错,加密方式是 SHA2+盐

//基于 Java 定义的配置文件
@Configuration
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	// 用户认证管理
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		
		auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
			.withUser("cc").password(passwd("1234")).roles("USER")
			.and()
	 		.withUser("admin").password(passwd("1111")).roles("USER", "ADMIN");

	}
	
	/**
	 * 密码编码
	 * 
	 * @param string 明文
	 * @return 密文
	 */
	private String passwd(String string) {
		// SHA2 + 盐【即:随机数据】
		return new BCryptPasswordEncoder().encode(string);
	}
	
	
	// 用户授权管理
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// TODO Auto-generated method stub
		 super.configure(http);
	}
}

④基于JDBC
配置文件中写入下面这几句话,连接数据库需要用到的:
在这里插入图片描述
新建一个配置类 DataSourceConfig,读取配置信息

@Component
@Configuration
public class DataSourceConfig {
	
	//Spring EL 读取了配置文件中的参数信息
	@Value("${spring.datasource.url}")
	String url;
	
	@Value("${spring.datasource.username}")
	String username;
	
	@Value("${spring.datasource.password}")
	String password;
	
	@Value("${spring.datasource.driver-class-name}")
	String driver;
	
	@Value("${newer.path}")
	String path;
	
	// Spring IoC 容器,工厂
	// 数据源的工厂
	// 需要数据源时调用
	@Bean
	public DataSource getDataSource() {
		System.out.println("读取了自定义的配置信息"+path);
		
		// Builder 模式
		return DataSourceBuilder.create()
				.driverClassName(driver)
				.url(url)
				.username(username)
				.password(password)
				.build();
	}

}

查看一下源码可以知道结构,至少需要建立两张表:users、authorities,从数据库中读取用户登录信息以及用户的角色

//基于 Java 定义的配置文件
@Configuration
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	
	// 依赖注入
	@Autowired
    DataSource dataSource;
	
	// 用户认证管理
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		
		auth.jdbcAuthentication().passwordEncoder(new BCryptPasswordEncoder())
		.dataSource(dataSource)
		.usersByUsernameQuery("select username,password,enabled from users where username = ?")
		.authoritiesByUsernameQuery("select username,authority from authorities where username = ?");

	}
	
	// 用户授权管理
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// TODO Auto-generated method stub
		 super.configure(http);
	}
}

表结构如下
在这里插入图片描述
user表,注意的是存入的密码是经过SHA2+盐加密处理的
在这里插入图片描述
authorities表,注意角色要大写且前缀是ROLE_
在这里插入图片描述

授权

了解了4中用户认证设置方式,下面看看重写的另一个重要的方法用户授权管理,认证方式是基于JDBC
1、简单写一个注册的页面,把注册的新用户存入数据库中,数据库中建4张表,实现新增用户时,3张表同时存入相应的数据,一张存用户基本信息、一张存用户登录信息、一张存用户角色【一个用户可以有多个角色】、还有一张是角色表【存放所有的角色】
index.html

<!doctype html>
<html lang="en">

<head>
    <title>Security</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>

<body>

    <div class="jumbotron jumbotron-fluid">
        <div class="container">
            <h1 class="display-3">Spring Security</h1>
            <p class="lead">用户认证、授权</p>
            <p>
                <a href="signup">用户注册</a>
            </p>
        </div>
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>

</html>

signup.html

<!doctype html>
<html lang="en">

<head>
    <title>用户注册</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>

<body>

    <div class="container">
        <div class="card mt-5">
            <div class="card-body">
                <h4 class="card-title">用户注册</h4>
                <!--基于表单提交-->
                <form action="/api/signup" method="post">
                    <div class="form-group">
                        <label for="">用户名</label>
                        <input type="text" class="form-control" name="name" placeholder="请输入用户名" required>
                    </div>
                    <div class="form-group">
                        <label for="">密码</label>
                        <input type="password" class="form-control" name="password" placeholder="请输入密码" maxlength="6" required>
                    </div>
                    <div class="form-group">
                        <label for="">角色</label>
                        <select class="form-control" name="title">
                    <option value="ROLE_MEMBER">会员</option>
                    <option value="ROLE_VIP">贵宾</option>
                    <option value="ROLE_VVIP">超级贵宾</option>
                    <option value="ROLE_ADMIN">管理员</option>
                    <option value="ROLE_NORMAL">普通游客</option>
                  </select>
                    </div>
                    <button type="submit" class="btn btn-primary btn-block">注册</button>
                </form>
            </div>
        </div>

    </div>
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>

</html>

mapper

@Mapper
public interface UserMapper {
	// 新增
	@Insert("insert into `user`(`name`) values(#{name})")
	void create(String name);
}
@Mapper
public interface AccountMapper {

	// 新增
	@Insert("insert into `account`(`name`,`password`) values(#{name},#{password})")
	void create(String name,String password);
}

用户角色表,角色这一列存的是角色的id,对应角色表中的 id

@Mapper
public interface UserRoleMapper {

	// 新增
	@Insert("insert into `user_role`(`name`,`role_id`) values(#{name},#{role_id})")
	void create(String name,int role_id);
}

@Mapper
public interface RoleMapper {

	// 根据角色名查询角色编号
	@Select("select id from role where title=#{title}")
	int findTitleByTitle(String title);
}

为了实现新增一个用户同时插入3个表中,新建一个Service

@Service
public class AccountService {

	@Autowired
	UserMapper userMapper;
	
	@Autowired
	AccountMapper accountMapper;
	
	@Autowired
	RoleMapper roleMapper;
	
	@Autowired
	UserRoleMapper userRoleMapper;
	
	public void insert(String name,String password,String title) {
		// 插入用户基本信息表
		userMapper.create(name);
		// 插入用户登录表
		accountMapper.create(name, new BCryptPasswordEncoder().encode(password));
		// 插入用户角色表
		userRoleMapper.create(name, roleMapper.findTitleByTitle(title));
	}
}

写几个控制器,简单测试一下
controller

@Controller
public class HomeController {

	// 返回 主页面
	@GetMapping
	public String home() {
		return "index.html";
	}
	
	// 返回 注册视图
	@GetMapping("/signup")
	public String signup() {
		return "signup.html";
	}
}

注册新用户:

@Controller
@RequestMapping("api")
public class AccontController {

	@Autowired
	AccountService accountService;

	// 注册成功,显示注册信息
	@PostMapping("/signup")
	@ResponseBody
	String signup(String name,String password,String title) {
		accountService.insert(name, password, title);
		return "欢迎您, "+name+ " 已注册成功,角色是 "+title;
	}

}

其他角色的控制器类似,这里就放admin的例子

@RestController
@RequestMapping("/admin")
public class AdminController {

	
	@GetMapping
	String admin() {
		return "这里是管理员控制台";
	}
	
	@GetMapping("/{name}")
	String admin(@PathVariable String name) {
		return "ADMIN:"+name+" 管理员控制台";
	}
	
}

重点是安全配置类,重写其中两个重要的方法,用户授权管理的方法中可以设置用户角色可以访问的资源

@Component
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	@Autowired
	DataSource dataSource;
	
	/*
	 * 用户认证
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//super.configure(auth);
		auth.jdbcAuthentication().passwordEncoder(new BCryptPasswordEncoder())
		.dataSource(dataSource)
		.usersByUsernameQuery("select `name`,`password`,`enabled` from `account` where `name` = ?")
		.authoritiesByUsernameQuery("select `name`,`title` from `user_role`,`role` where `role_id`=`id` and `name` = ? ");
		
		
	}
	
	
	/*
	 * 用户授权
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
	
		// 启动 POST 提交
		http.csrf().disable();
		
		http.authorizeRequests()
		.antMatchers("/","/api/signup","/signup").permitAll()
		.antMatchers("/member","/member/*").hasAnyRole("VIP","VVIP","MEMBER")
		.antMatchers("/vip","/vip/*").hasAnyRole("VIP","VVIP")
		.antMatchers("/vvip","/vvip/*").hasRole("VVIP")
		.antMatchers("/admin","/admin/*").hasRole("ADMIN")		
		.anyRequest().authenticated()
		.and()
		.formLogin().and().httpBasic();
	}
	
}

会话管理

  • HTTP 请求是基于 请求-响应 的通信模型,浏览器发出请求,服务器做出响应,一个会话就结束了。

  • HTTP 协议,是一种无状态协议【不保存状态】,本身不对请求和 响应之间的通信状态进行保存,对于发送过的请求和响应都不做持久化处理。

  • 缺陷:Spring Security 基于session,为Web应用设计的,如果前后端分离则不可用,需要 token【支持前后端分离】。

会话管理技术

为了保存状态,浏览器 和 服务端 技术提供了状态的管理

  • cookie 【浏览器提供的】
  • session【服务器提供的,基于cookie实现】
  • url encoding【服务器提供,解决cookie被禁用的情况】
  • token 【令牌 支持移动端及前后端分离请查看我的另一边文章 JWT
  • OAuth 开发认证

cookie

  • 服务端生成的文本格式的键值对,随响应头发送给用户浏览器
    • 1)写入用户磁盘【存在有效期】
    • 2)驻留在用户浏览器的内存空间【临时的】
  • 浏览器再次请求该域名时,浏览器会在HTTP请求头自动携带该域名的cookie发送给服务器
  • 浏览器中可以禁用cookie,下次发送请求时不携带cookie,但其实浏览器发生了cookie,只是用户没接收,使用url encoding 可以解决

session

  • 基于 jsessionid 的 cookie 实现,用户的状态存储在服务端
  • 浏览器在第一次访问时,服务器会创建一个名为jsessionid且值唯一的cookie,服务器会在内存中创建一个hashmap,每一个jsessionid都有一个hashmap,可以存储用户的状态信息,session是存放在服务端的,默认有效期 TTL 为30分钟
  • 浏览器再次访问服务器时,会在请求它携带jsessionid,到达服务器后可以根据jsessionid 读取hashmap中的数据,并进行了TTL续约,又重新获得30分钟的有效期
  • 会话超时,服务器会消耗session
  • 会话状态 持久化:可以考虑写入数据库,否则有效期只有30min

session 与 cookie 的关系与区别

  • 关系:session 是基于 cookie 实现的
  • 区别:
    • cookie 是由浏览器支持,文本格式数据,存储在客户的磁盘中,不占用服务器资源,存在 有效期
    • session 是由服务器提供的,hashmap 格式,存储在服务端的内存中,占用服务器资源,超时 不可用【TTL = 30 min】
    • cookie 可以被用户禁用,大小4K,不存储敏感数据
    • session 不能禁用,因为是服务器提供的
    • 用户关闭浏览器,cookie 可以继续使用,而 session 不可用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值