1.概述
在后端来说,安全主要就是控制用户访问,让对应权限的用户能访问到对应的资源,主要是两点:
认证
授权
认证,确定是谁。
授权,核实权限。
每个安全框架其实都是为了实现这两点。
目前常用的实现方式有如下几种:
token
JWT
oauth
spring security
前三种是理念,最后一种是开箱即食的框架。
2.token
2.1.理论
token ,也叫“令牌”,是验证用户身份的凭证。token的组成具有随意性,能标识用户身份即可。
token的工作流程:
客户端向服务器发送请求,服务器收到后生成token返回给客户端,此后客户端的任何鉴权都基于该token进行。
2.2.使用
以下是一个简单的在Spring Boot中用token检验用户是否合法的一个代码示例。当然实际工作中token里面可以放很多内容,比如可以放权限,后端解析后基于权限来进行鉴权,也可以给token里面加上时限等相关信息,用来控制token的有效期等。
token工具类:
controller:
3.JWT
3.1.理论
JWT,Json web token,即一种基于json的通用token标准,token本质上具有任意性,JWT规范了token的格式。
JWT 规定token由三部分组成:
header
头部,承载两块信息:
声明类型,即声明这是jwt
声明加密算法,默认使用 HMAC SHA256加密算法
payload
载荷,存放有效信息(数据信息)的地方。
signature
签证,可以用于验证整个token的完整性以及是否被篡改,由三部分组成:
header(base64之后的)
payload(base64之后的)
secret私钥
3.2.使用
JWT本质上就是个有标准报文格式的token,其实只要愿意的话我们可以自己手写一个,这个没什么难度,定义一个类罢了。JWT市面上也有很多开源实现,直接拿来用就行了,以下是使用Spring Boot+JJWT来实现鉴权的一个简单demo,不用深究,就是感受一下别人的实现。
工具类:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; import java.util.function.Function; @Component public class JwtUtil { // 读取配置文件中的JWT秘钥 @Value("${jwt.secret}") private String secret; // JWT的过期时间,单位毫秒 private final long JWT_EXPIRATION = 1000 * 60 * 60 * 24; /** * 生成JWT * @param username 用户名 * @return JWT */ public String generateToken(String username) { // 设置JWT的过期时间为当前时间 + JWT_EXPIRATION Date expiration = new Date(System.currentTimeMillis() + JWT_EXPIRATION); // 使用JWT的Builder类构造JWT return Jwts.builder() .setSubject(username) // 设置JWT的主题为用户名 .setIssuedAt(new Date()) // 设置JWT的发行时间为当前时间 .setExpiration(expiration) // 设置JWT的过期时间 .signWith(SignatureAlgorithm.HS256, secret) // 使用HS256算法和秘钥对JWT进行签名 .compact(); // 构造JWT并返回 } /** * 验证JWT是否有效 * @param token JWT * @return JWT是否有效 */ public boolean validateToken(String token) { try { // 使用JWT的parser类解析JWT Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); // 如果JWT的过期时间早于当前时间,则JWT无效 Date expiration = claims.getExpiration(); if (expiration.before(new Date())) { return false; } // JWT有效 return true; } catch (Exception e) { // 解析JWT失败,则JWT无效 return false; } } /** * 从JWT中获取主题 * @param token JWT * @return 主题 */ public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } /** * 从JWT中获取指定claim的值 * @param token JWT * @param claimsResolver 指定claim的解析器 * @param <T> 指定claim的类型 * @return 指定claim的值 */ private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } /** * 解析JWT中的所有claim * @param token JWT * @return 包含所有claim的Claims对象 */ private Claims extractAllClaims(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }
控制器:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private JwtUtil jwtUtil; @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) { // TODO: 实现用户登录逻辑 String username = "exampleUser"; String token = jwtUtil.generateToken(username); return ResponseEntity.ok(new JwtResponse(token)); } @GetMapping("/validateToken") public ResponseEntity<?> validateToken(@RequestParam("token") String token) { if (jwtUtil.validateToken(token)) { return ResponseEntity.ok().build(); } else { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } private static class LoginRequest { private String username; private String password; // getters and setters } private static class JwtResponse { private String token; public JwtResponse(String token) { this.token = token; } // getter } }
4.oauth
OAUTH,Open Authorization,开放授权协议,为用户资源的授权提供了一个安全的、开放而又简易的标准。目的是让第三方对用户的数据只有有限访问权,而无法触及到用户的核心信息。
例如,在第三方网站上使用微信或者QQ作为账号进行登录,就是使用的oauth协议,只返回给第三方注入用户名、头像等信息,而不会返回给第三方秘密等核心数据。
注意:oauth这里暂时不做展开,也不提供代码实现示例,因为展开的话篇幅会比较长,这里只是简单提一下这个概念,下一篇博文,博主会专门写oauth。
以下是一个用QQ在慕课网上做oauth的流程示例,大家感受一下:
5.Spring Security
5.1.概述
Spring Security是一个基于Spring框架的安全框架,它提供了一系列的安全服务和管理应用程序安全的能力。Spring Security的主要目标是保护应用程序,防止未经授权的访问,同时支持常见的认证和授权方案。
注意:由于本文只是介绍一下一些可以和Spring Boot结合起来使用的安全方案,Spring Security我们只限定于讲解基础使用,下下篇文章,作者会专门用一个大篇幅来详细写Spring Security。
5.2.基本认证授权
依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
配置:
配置一个基于内存的用户存储库,其中包含两个用户("user"和"admin"),并使用密码编码器"{noop}"指定它们的密码。该配置还指定了"/admin/**"端点需要ADMIN角色才能访问,并且所有其他端点需要进行身份验证。
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // 配置内存中的用户存储库 @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password("{noop}password").roles("USER") .and() .withUser("admin").password("{noop}password").roles("USER", "ADMIN"); // 创建两个用户: "user"和"admin",使用密码编码器"{noop}"指定密码 } // 配置HTTP请求的安全性 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") // /admin/**端点需要ADMIN角色 .anyRequest().authenticated() // 其他端点需要进行身份验证 .and() .formLogin() // 启用基于表单的登录 .and() .httpBasic(); // 启用HTTP基本认证 } }
用户的角色除了可以放在内存中,还可以放在其它存储介质上,如果有需要的话可以存在数据库:
控制器:
@RestController @RequestMapping("/admin") public class AdminController { @GetMapping("/") public String adminHome() { return "Welcome to the admin page!"; } }
5.3.加密
Spring Security提供了多种加密方式,包括以下几种:
BCryptPasswordEncoder:这是最常用的加密算法之一,它使用哈希和随机盐来加密密码。
Pbkdf2PasswordEncoder:这也是一种密码加密算法,它使用基于密码的密钥导出函数(PBKDF2)来加密密码。
SCryptPasswordEncoder:这是一种基于内存的密码哈希算法,它使用大量的内存来防止散列碰撞攻击。
NoOpPasswordEncoder:这是一种不安全的加密算法,它仅仅是将明文密码作为加密后的密码。不建议在生产环境中使用。
使用Spring Security进行加密通常需要以下几个步骤:
在Spring Security配置中定义PasswordEncoder bean。
使用PasswordEncoder bean对用户密码进行加密,然后将其保存到数据库中。
在身份验证过程中,Spring Security会将用户输入的密码与加密后的密码进行比较,以确定用户是否有权访问该资源。
以下是一个使用BCryptPasswordEncoder加密密码的示例:
spring security对支持的不同加密算法提供了不同的Encoder。
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // ...其他配置 }
使用encoder进行加密:
@Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password(passwordEncoder.encode("password")).roles("USER") .and() .withUser("admin").password(passwordEncoder.encode("password")).roles("USER", "ADMIN"); }