文章目录
一、主要内容
1. SpringSecurity的加密解密
2. JWT鉴权
3. JWT的鉴权使用
二、SpringSecurity加密解密
1.pom依赖
<!-- SpringSecurity 权限 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.SpringSecurity配置类
因为SpringBoot已经集成了SpringSecurity,我们只要导入其依赖就会默认拦截全部请求.
需要使用配置类对其默认配置进行修改。
package lsh.config;
import org.springframework.context.annotation.Configuration;
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;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 10:45 上午
* @desc :
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//authorizeRequests所有security全注解配置实现的开端,表示开始说明需要的权限。
//需要的权限分两部分,第一部分是拦截的路径,第二部分访问该路径需要的权限。
//antMatchers表示拦截什么路径,permitAll任何权限都可以访问,直接放行所有。
//anyRequest()任何的请求,authenticated认证后才能访问
//.and().csrf().disable();固定写法,表示使csrf拦截失效。
http
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
}
3.Controller
@RestController
@RequestMapping("/admin")
public class AdminController {
@Autowired
AdminService service;
@PostMapping("/addAdmin")
public ResultObject addAdmin(@RequestBody Admin admin){
service.addAdmin(admin);
return new ResultObject(true, StatusCode.OK,"添加管理员成功",null);
}
@PostMapping(value = "/login")
public ResultObject login(@RequestBody Admin admin){
Admin adminLogin = service.login(admin);
if(adminLogin==null){
return new ResultObject(false, StatusCode.LOGINERROR, "登录失败");
}
return new ResultObject(true, StatusCode.OK, "登录成功");
}
}
4.Service
public interface AdminService {
void addAdmin(Admin admin);
Admin login(Admin admin);
}
package lsh.service.Impl;
import com.lsh.util.IdWorker;
import lsh.model.Admin;
import lsh.repository.AdminRepository;
import lsh.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 1:43 下午
* @desc :
*/
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
AdminRepository adminRepository;
@Autowired
IdWorker idWorker;
/**
* 加解密
*/
@Autowired
private BCryptPasswordEncoder encoder;
/**
* 使用SpringSecurity的BCryptPasswordEncoder进行加密
* @param admin
*/
@Override
public void addAdmin(Admin admin) {
admin.setId( idWorker.nextId()+"" );
//密码加密
admin.setPassword(encoder.encode(admin.getPassword()));
adminRepository.save(admin);
}
@Override
public Admin login(Admin admin) {
//先根据用户名查询对象。
Admin adminLogin = adminRepository.findByLoginname(admin.getLoginname());
//然后拿数据库中的密码和用户输入的密码匹配是否相同。
if(adminLogin!=null && encoder.matches(admin.getPassword(), adminLogin.getPassword())){
//保证数据库中的对象中的密码和用户输入的密码是一致的。登录成功
return adminLogin;
}
//登录失败
return null;
}
}
5.Repository
public interface AdminRepository extends JpaRepository<Admin,String>, JpaSpecificationExecutor<Admin> {
Admin findByLoginname(String loginname);
}
6.Model
package lsh.model;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 10:48 上午
* @desc :
*/
@Data
@Entity
@Table(name="tb_admin")
public class Admin implements Serializable {
/**
* ID
*/
@Id
private String id;
/**
* 登陆名称
*/
private String loginname;
/**
* 密码
*/
private String password;
/**
* 状态
*/
private String state;
}
三、JWT
JSON Web Token
JWT是一种在两方之间传输信息的方法。在JWT的主体中编码的信息被称为claims。JWT的扩展形式是JSON格式,因此每个claim都是JSON对象中的一个键。
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
1.JWT的组成
一个JWT实际上就是一个字符串,它由三部分组成,
头部Header、载荷Payload、签名Signature。
头部
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。头部被表示成一个JSON对象。
载荷
载荷就是存放有效信息的地方:
比如登录的用户名,登录时间,登录过期时间,自定义数据(角色信息等等)
一个常见的载荷的写法
签名
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64加密后的)
payload (base64加密后的)
secret // 盐,也是一个秘钥
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
注意:
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
将这三部分用.连接成一个完整的字符串,构成了最终的jwt
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoi5byg5LiJIiwiaWF0IjoxNjA0Mzg4ODM2LCJyb2xlcyI6ImFkbWluIiwicGVybWlzc2lvbiI6ImRlbGV0ZV9hbnl0aGluZyIsImV4cCI6MTYwNDQ3NTIzNn0.ptle3Y2065SljyNU6hNQuChEYUSwLaw29h_EDGu1zvw
登录生成token
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是
这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
-
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向
客户端返回请求的数据
以后每次访问的认证
客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API);
认证服务作为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找;
如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;
完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;
全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;
2.Java的JJWT认证实现
JJWT是一个提供端到端的JWT创建和验证的Java库。
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-configuration-processor </artifactId>
<optional> true </optional>
</dependency>
</dependencies>
测试类代码
package com.lsh;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 2:27 下午
* @desc :
*/
@SpringBootTest
public class JwtTest {
public static final String key = "zhiyou";
public static final Integer ttl = 10000;
@Test
public void test(){
String jwt = createJWT("1", "张三", "admin", "delete_anything");
System.out.println("jwt:"+jwt);
Claims claims = parseJWT(jwt);
System.out.println("claims:"+claims.toString());
}
public String createJWT(String id, String subject, String roles,String permission) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)//主题信息
.setIssuer("LiuShihao")//颁发者
.setIssuedAt(now)//办法时间
.signWith(SignatureAlgorithm.HS256, key);//签名算法 盐
//可以使用这种方式自定义载荷信息,也可以使用下面的方式
// .claim("roles", roles)
// .claim("permission",permission);
//自定义载荷信息
Map<String, Object> userinfo=new HashMap<string, Object>;
userinfo.put(" company","集团号百");
userinfo.put(" address","上海");
userinfo.put(money", 3500);
//添加载荷
builder.addclaims(userinfo);
if (ttl > 0) {
builder.setExpiration( new Date( nowMillis + ttl));
}
return builder.compact();
}
public Claims parseJWT(String jwtStr){
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
}
}
封装JwtUtil工具类
yml配置文件
jwt:
config:
key: zhiyou100
# 有效时间24*60*60*1000
ttl: 86400000
package com.lsh.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 2:16 下午
* @desc :
*/
@Data
@Component
@ConfigurationProperties(prefix ="jwt.config")
public class JwtUtil {
private String key ;
private long ttl ;
/**
* 生成JWT Token
*/
public String createJWT(String id, String subject, String roles,String permission) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, key)
.claim("roles", roles)
.claim("permission",permission);
if (ttl > 0) {
builder.setExpiration( new Date( nowMillis + ttl));
}
return builder.compact();
}
/**
* 解析JWT
*/
public Claims parseJWT(String jwtStr){
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
}
}
Token过期报错:
3.修改登陆逻辑
- 登录成功的
- 生成jwt令牌
- 把令牌信息存入到Cookie
- 把令牌作为参数传给用户
/**
* 登录成功的同时生成jwt令牌,令牌交给前端处理
* @param admin
* @return
*/
@PostMapping(value = "/login")
public ResultObject login(@RequestBody Admin admin){
Admin adminLogin = service.login(admin);
if(adminLogin==null){
return new ResultObject(false, StatusCode.LOGINERROR, "登录失败");
}
String jwt = jwtUtil.createJWT(adminLogin.getId(), adminLogin.getLoginname(), "admin", "crud");
Map<String, Object> map = new HashMap<>();
map.put("token", jwt);
map.put("admin",adminLogin);
return new ResultObject(true, StatusCode.OK, "登录成功",map);
}
4.操作鉴权
比如删除操作
Controller
@DeleteMapping
public ResultObject deleteById(String id){
return service.deleteById(id);
}
Service
ResultObject deleteById(String id);
ServiceImpl
/**
* 删除 必须有admin角色才能删除
*/
@Override
public ResultObject deleteById(String id) {
String authorization = request.getHeader("Authorization");
if (authorization == null) {
return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,没有授权");
} else {
if (!authorization.startsWith("Bearer")) {
return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,错误的Token");
} else {
//提取token
String token = authorization.substring(7);
Claims claims = null;
try{
claims = jwtUtil.parseJWT(token);
}catch (MalformedJwtException e){
return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,Token解析错误");
}
log.info("claims:"+claims);
if (claims == null) {
return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,Token解析为空");
} else {
if ((!"admin".equals(claims.get("roles")))) {
return new ResultObject(false, StatusCode.ACCESSERROR, "权限不足,不是管理员角色");
}else {
adminRepository.deleteById(id);
return new ResultObject(false, StatusCode.ACCESSERROR, "删除成功");
}
}
}
}
}
测试:
四、使用拦截器实现token鉴权
使用SpringBoot提供的拦截器.在pre方法中对请求进行拦截.获取请求头,鉴权!
如果鉴权失败,抛出异常
鉴权成功,将角色信息放入请求域,后续操作从请求域获取数据即可!
1.自定义拦截器
package com.lsh.interceptor;
import com.lsh.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 4:50 下午
* @desc :拦截器 对 Token 鉴权
*/
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("经过JwtInterceptor拦截器");
//拦截器只是负责把头请求头中包含token的令牌进行一个解析验证。
String header = request.getHeader("Authorization");
if(header!=null && !"".equals(header)){
//如果有包含有Authorization头信息,就对其进行解析
if(header.startsWith("Bearer")){
//得到token
String token = header.substring(7);
//对令牌进行验证
try {
Claims claims = jwtUtil.parseJWT(token);
String roles = (String) claims.get("roles");
if(roles!=null && roles.equals("admin")){
request.setAttribute("claims_admin", token);
}
if(roles!=null && roles.equals("user")){
request.setAttribute("claims_user", token);
}
}catch (Exception e){
throw new RuntimeException("令牌不正确!");
}
}
}else {
throw new RuntimeException("令牌为空!");
}
//无论如何都放行。具体能不能操作还是在具体的操作中去判断。
return true;
}
}
2.将自定义拦截器注册进Spring容器
package com.lsh.config;
import com.lsh.interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 4:52 下午
* @desc :将JwtInterceptor注册到Spring容器
*/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//注册拦截器要声明拦截器对象和要拦截的请求
registry.addInterceptor(jwtInterceptor)
//拦截所有请求 进入JwtInterceptor拦截器
.addPathPatterns("/**")
//排除登陆请求
.excludePathPatterns("/**/login/**");
}
}