1、登录业务
SSO(Single Sign on)模式
淘宝天猫单点登录原理:
这个sso模型与游乐场的门票是一个道理的,凭证token可以访问任意服务,不能泄漏,更安全的解决方案:
1、缓存定时失效
2、两次登录信息不一致(电脑,手机),进行短信验证码验证,如支付宝
2、集成JWT
登录后,传统的身份验证,可以在拦截器或过滤器进行身份验证,但是这样网站服务与身份验证在同一个工程中,耦合性强,单体应用可以使用,但是多个微服务下就不行了,因为无法横向扩展,解决方案:
- session共享,服务太多会存在共享延迟的问题,可以把session缓存到redis中。
- cookie令牌,用户信息缓存到第三方,代表解决方案 JWT(JSON Web Token)
JWT令牌
-
透明令牌
随机生成,没有办法猜测这个是如何存储和颁发!所有的请求必须要要到 OAuth2授权服务的的检测,才能判断这个令牌是否有效!
-
自包含令牌
授权服务器颁发的,包含用户的数据,声明式检查前面,颁发者、期望的接收人,,,,, 可以在本地来进行校验!
JWT组成
JWT对象是一个很长的字符串,通过.来进行分割为三个字符串!
1、Header就是JWT 元数据的JSON对象
{
"alg": "HS256",
"type": "JWT"
.....
}
2、Claims就是有效载荷(具体的主体信息)
7个默认的字段:
发行人
到期时间
主题
用户
在此之前不可用
发布时间
jwt id
我们还可以添加自己的信息
{
"name": "coding",
///...
}
3、Signature就是签名哈希
签名哈希 就是对上面两部分数据的签名,通过指定的算法,生成哈希,确保数据不会被篡改!
首先,需要指定一个密码(secret),保存到服务器中,secret不能向用户公开。
根据某种算法,生成一个新的密码。
JWT的好处
-
不仅可以用于认证,还可以用来交换信息。可以减少请求服务器的次数!
- (自包含令牌)生成的token一般会包含用户的信息,id/昵称、头像…..,
- 存储在客户端的,不会占用服务器端的内存资源!
JWT的缺点
- JWT 默认不加密的,你也可以加密,生成原始的令牌后然后对其再加密!同理,服务器拿到token后先解密得到明文的token字符串,再解析与校验token,所以没有被加密的时候,不建议存放私密数据!
JWT头与calims载荷默认不加密的,通过nimbus-jose-jwt这个开源库解析出payload载荷信息,包括jti、exp到期时间、主题、自己添加的信息等,无需通过签名校验token,就可以获取这些payload信息,为了避免token被篡改,一般都是先用Signature(签名)校验token再解析token获取payload。
nimbus-jose-jwt 使用他可以生成或者解析对称加密或者非对称加密的的JWT. JWS是JWT规范的落地实现。
集成到项目中
用户中心模块 edu-ucenter
1、pom.xml导入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
2、创建工具类JwtUtils
public class JwtUtils {
// 这个只有在服务端有
public static final String APP_SECRET = "[asdhakdjsakjdlasjdu13123123_123!@";
public static final long EXPIRE = 1000*60*60*24;
public static String getJwtToken(String id,String nickname){
String jwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("member-user") // 这个token字符串的标题,也可以使用id唯一值标识
.setIssuedAt(new Date()) // 创建时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id) // 自包含的数据
.claim("nickname", nickname) // 自包含的数据
.signWith(SignatureAlgorithm.HS256, APP_SECRET) // 签名加密
.compact();
// 就是一个指定规则的字符串jwtToken,这里保存用户的信息
return jwtToken;
}
// 检测token
public static boolean checkToken(String jwtToken){
if (StringUtils.isEmpty(jwtToken)) return false;
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
// 根据用户请求头中的token获取用户的id
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return (String) body.get("id");
}
}
前台项目就可以使用jwt,给用户生成一个加密的token,服务端进行校验解析。
3、阿里云短信验证码
开通服务
阿里云业务模型:
这里使用跟oss\vod同一个授权子账号,
登录阿里云控制台
短信业务开通
点击进去,开通
给用户组授权短信业务AliyunDysmsFullAccess
计费规则
短信规则
超过70个字数,按两条短信计算,
计费规则
https://www.aliyun.com/price/product?spm=5176.8911205.0.0.1d541cbepQsxQP#/sms/detail
上限规则
签名与短信模版申请
个人只配拥有一个签名,模版和签名申请一定要正规,否则通过不了。
4、短信微服务
短信模版申请通过后,我们在项目中集成短信服务,找到短信服务的帮助文档,添加依赖
使用示例:
新建module maven工程edu-sms 短信微服务
发送短信验证码,测试
public class TestSMS {
@Test
public void test(){
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "<accessKeyId>", "<accessSecret>");
// 连接到客户端
IAcsClient client = new DefaultAcsClient(profile);
// 短信请求
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain("dysmsapi.aliyuncs.com");// 不能改动
request.setSysVersion("2017-05-25"); // 版本时间,不能改动
// api事件
request.setSysAction("SendSms");
// 具体的发送
request.putQueryParameter("PhoneNumbers", "187xxxx39");
request.putQueryParameter("SignName", "<签名>");
request.putQueryParameter("TemplateCode", "<模版CODE>");
// 验证码
HashMap<String, Object> param = new HashMap<>();
param.put("code",2333);
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));
try {
// 请求发送获得响应
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
}
}
测试没问题,写接口服务
web层接口的定义SmsApiController
@GetMapping("/send/{phone}")
public R code(@PathVariable("phone") String phone){
// 先判断否已经发送过了,从redis获取key
String code = redisTemplate.opsForValue().get(phone);
if (!StringUtils.isEmpty(code)){
return R.ok();
}
// 生成验证码
code = UUID.randomUUID().toString().substring(0, 4);
HashMap<String, Object> param = new HashMap<>();
param.put("code",code);
boolean isSend = smsService.send(phone, "SMS_189520818", param); // SMS_189520818 就是短信模版CODE
if (isSend){
// 缓存到redis中,60秒失效
redisTemplate.opsForValue().set(phone,code,60,TimeUnit.SECONDS);
return R.ok();
}else {
return R.error().message("短信发送失败");
}
}
业务层实现类SmsServiceImpl.java
@Service
public class SmsServiceImpl implements SmsService {
@Override
public boolean send(String phoneNums, String tempalteCode, Map<String, Object> param) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", ConstantPropertiesUtils.ACCESS_KEY_ID, ConstantPropertiesUtils.ACCESS_KEY_SECRET);
// 连接到客户端
IAcsClient client = new DefaultAcsClient(profile);
// 短信请求
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setDomain("dysmsapi.aliyuncs.com");// 不能改动
request.setVersion("2017-05-25"); // 版本时间,不能改动
// api事件
request.setAction("SendSms");
// 具体的发送
request.putQueryParameter("PhoneNumbers", phoneNums);
request.putQueryParameter("SignName", "狂神说Java学习网站");
request.putQueryParameter("TemplateCode", tempalteCode);
// 验证码
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));
try {
// 请求发送获得响应
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
return response.getHttpResponse().isSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return false;
}
}
5、理解OAuth2协议
什么是oauth2
OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息。 OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0
oauth2的清楚认识,查看《OAuth2最简向导》
oauth2的应用
主要应用于现代微服务安全
-
传统的单体应用安全
应用服务器保存了授权过的用户session信息
-
现代微服务安全
核心的技术不是用户名和密码,而是token,由AuthServer颁发token,用户使用token进行登录
典型应用
oauth2解决的问题
6、整合微信登录
第三方应用使用微信登录的流程如下:
主要理解为两步:
第一步:请求CODE(生成授权URL)
第二步:通过code获取access_token(开发回调URL)