Springboot在使用JWT单点登录的时候,尽量使用Https协议,为了实现JWT单点登录前,需要搭建含有SSL证书的互通方式,为了模拟项目,采用JDK提供的自签的方式,生成SSL自签证书,并集成到Springboot项目中,如果已经有企业SSL证书,跳过即可
JDK生成自签证书
在cmd命令下运行jdk自带的keytool工具(没有配置jdk环境变量的需要在jdk的bin目录下):
keytool -genkey -alias mypfx -keypass 111111 -storetype PKCS12 -keyalg RSA -keysize 1024 -validity 365 -keystore D:/Git/mypfx.p12 -storepass 111111
参数解释:
- alias mypfx(证书别名)
- keypass 111111(别名密码)
- keyalg RSA(算法)
- keysize 1024(密钥长度)
- validity 365(有效期,天单位)
- keystore D:/Git/mypfx.p12(指定生成证书的位置和证书名称)
- storepass 111111(获取证书信息的密码)
- storetype 指定密钥仓库类型
内容根据个人情况输入即可,我这里随便填写了几个
以上是我用jdk生成自签SSL证书的过程,具体还需要自己去了解SSL证书的原理
Springboot采用JWT实现单点登录
搭建springboot项目,实现https协议
构建基础springboot项目,springboot版本:2.1.3.RELEASE
在application.properties配置文件中加入刚才生成的自签文件
server.port=443
#证书文件路径
server.ssl.key-store=D:/Git/mypfx.p12
#别名
server.ssl.key-alias=mypfx
server.ssl.enabled=true
#密码
server.ssl.key-store-password=111111
#证书类型
server.ssl.key-store-type=PKCS12
添加config配置文件,实现http协议自动跳转到https访问
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Https协议端口号配置
*
* @author AuroraLove
* @date 2019/3/29
*/
@Configuration
public class HttpsConfiguration {
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
};
tomcat.addAdditionalTomcatConnectors(httpConnector());
return tomcat;
}
private Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
//Connector监听的http的端口号
connector.setPort(80);
connector.setSecure(false);
//监听到http的端口号后转向到的https的端口号
connector.setRedirectPort(443);
return connector;
}
}
运行启动项目测试:
前面做了那么多准备工作,接下来开始搭建JWT单点登录(重头戏来了)
JWT单点登录
JWT单点登录在分布式系统,微服务架构中应用广泛,他由传统的Sesion会话管理变为了服务器自签认证,是一种无状态的验证登录,原理我就不多说了,网上都可以找到,但是必须要对JWT信息有一定的基本了解:
eyJ1aWQiOjE5NTAyLCJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJpc3MiOiJodHRwOi8vd3d3LmJsdWV0aGluay5jbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2IiwiaWF0IjoxNTUzODQ0OTY0fQ
.RdHEmhdwNrvz4dzD3fmCBbr9ye-O1R96MzzNmQX3kwI
上面这个JWT 的三个部分依次如下,信息以"."为分隔
- Header(头部)
- Payload(负载)
- Signature(签名)
这三部分中,头部是保存签名算法以及令牌类型信息的,中间负载则是存放用户自定义信息或官方定义的信息,尾部签名则是服务器端添加的签名,确保前两部分的安全
有了基本的信息了解之后,开始搭建项目:
我的项目结构图:
增加pom依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
自定义token注解
/**
* 自定义token注解
*
* @author AuroraLove
* @date 2019/3/29
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
boolean required() default true;
}
用户登录验证成功时,生成token秘钥(为了便于验证,连接mysql数据库,建立一张简单user表)
/**
* @author AuroraLove
* @date 2019/3/29
*/
@Service
public class UserService {
@Autowired
private static Logger logger = LoggerFactory.getLogger(UserService.class);
@Autowired
private UserMapper userMapper;
public UserModel login(UserFilter ufilter){
Optional<UserEntity> user = userMapper.findByPhone(ufilter.getPhone());
if (user.isPresent()){
if (user.get().getPassword().equals(ufilter.getPassword())){
//验证用户登录成功后,生成token令牌
String token = getToken(user.get());
logger.info("token:" + token);
UserModel userModel = new UserModel(user.get(),token);
return userModel;
}
}
return null;
}
/**
* 生成token
* @param userEntity
* @return
*/
private String getToken(UserEntity userEntity) {
//设置算法为HS256
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
Date now = new Date(System.currentTimeMillis());
JwtBuilder builder = Jwts.builder()
//设置header
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
//设置iat
.setIssuedAt(now)
//设置payload的键值对
.claim("name", userEntity.getName())
.claim("id", userEntity.getId())
.claim("phone", userEntity.getPhone())
.setIssuer("AuroraLove")
//签名,需要算法和key
.signWith(signatureAlgorithm, SecretKey.SERVER_KEY);
String jwt = builder.compact();
return jwt;
}
}
增加拦截器,对token注解进行全局拦截验证
设置拦截器配置文件:
/**
* 拦截器配置文件
*
* @author AuroraLove
* @date 2019/3/29
*/
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有请求,通过判断是否有 @Token 注解
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
新增拦截器
/**
* 拦截器
*
* @author AuroraLove
* @date 2019/3/29
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
//检查是否有token注释,有则认证
if (method.isAnnotationPresent(Token.class)) {
Token tokenAnnotation = method.getAnnotation(Token.class);
if (tokenAnnotation.required()) {
//从请求头中取出token参数
String token = httpServletRequest.getHeader("token");
if (token == null){
throw new Exception("token信息不存在!");
}
//解析token参数
Map<String, Claim> claims = JWT.decode(token).getClaims();
Long id = claims.get("id").asLong();
//验证用户是否存在
if (userService.verify(id)){
return true;
}
throw new Exception("token信息验证失败");
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
验证结果:
伪造失败token
成功结果