由于对智能家居物联网比较感兴趣,之前参考电子发烧友上 小狂的教程,加上自己的想法,完善一版基于PHP的智能家居的设备平台实现,
且天猫精灵的开放性和功能是国内智能音箱中的佼佼者,于是准备用java重构之前被魔改混乱的php代码。
实现OAuth2.0接入:
参考文档:天猫精灵开放平台OAuth2.0
(我是照着官网文档写的流程,该授权服务并不能用于其他站点登陆,需要修改)
需要实现5个基本类:
Authorize.java //认证服务,用来发放code授权码
GetToken.java //授权服务,用来发放token访问令牌
CodeFactory.java //生成随机code和token
TokenBean.java //为了生成携带token的json数据
SqlOperat.java //提供与数据库进行存取数据接口
此处实现的是授权码模式,具体流程为:
1、用户点击打开授权请求的按钮/链接(url携带回调地址redirect_uri和一些信息)
2、浏览器用这个链接向Authorize请求授权页面
3、Authorize响应授权页面给浏览器并展示给用户
4、用户点击确认授权
5、Authorize收到用户确认后生成code授权码加上state参数,用302重定向到redirect_uri地址
6、浏览器收到后进行重定向带上code+state提交给redirect_uri这个地址(Aligenie的服务器)
7、Aligenie的服务器收到code后带上这个code参数 向GetToken请求申请token
8、GetToken对code的正确性和有效性进行验证,验证通过返回给Aligenie的服务器数据如下
{
"access_token": "XXXXXX", //访问令牌token,用来请求资源或者服务,就是通行证
"refresh_token": "XXXXXX", //获取下一次token的token,就是通行证过期后用它换新的通行证
"expires_in":17600000 //token的有效期
}
至此,授权过程完成
* * * * *No img you say JB* * * * *
下面测试授权成功:
回归实现,下面附上代码:
认证服务:
/**
* @author : willian fu
* @date : 2019-02-14.
* OAUTH授权服务
*/
@WebServlet("/authorize")
public class Authorize extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
System.out.println(req.getRequestURI());
HttpSession session = req.getSession();
//如果点击了确认授权
if ("yes".equals(req.getParameter("authorize"))){
//确认了授权,获取第三方接收code的重定向地址
String redirect_uri = (String) session.getAttribute("redirect_uri");
//System.out.println(redirect_uri);
String state = (String) session.getAttribute("state");
//生成code授权码
String code = CodeFactory.getCode();
String client_id = (String) session.getAttribute("client_id");
//发布访问令牌,解码url并且将code加在url后面返回
redirect_uri = UrlUtil.getURLDecoderString(redirect_uri)+"&code="+code+"&state="+state;
System.out.println(redirect_uri);
//保存code到数据库
SqlOperat.savaAccessCode(code, redirect_uri, client_id);
resp.setStatus(302);
resp.setHeader("location", redirect_uri);
}else if ("no".equals(req.getParameter("authorize"))){
resp.setHeader("refresh","2;url=/authorize");
resp.getWriter().write("<h2 style='text-align:center'>授权取消,请重新授权!</h2>");
}else {
//缓存重定向redirect_url地址到session
session.setAttribute("redirect_uri",req.getParameter("redirect_uri"));
session.setAttribute("state",req.getParameter("state"));
session.setAttribute("client_id",req.getParameter("client_id"));
//如果是授权请求,则展示授权页面
System.out.println("redirect_uri---"+session.getAttribute("redirect_uri"));
req.getRequestDispatcher("oauthShow.html").forward(req,resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
授权服务:
/**
* @author uthor : willian fu
* @date : 2019-02-14.
* Token发放服务
* errorCode :
* 1000 code过期
* 2000 grant_type类型错误
* 3000 OAuth用户名/密码错误
*/
@WebServlet("/token")
public class GetToken extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
//resp.setContentType("text/html;charset=utf-8");
System.out.println("hello token!");
String client_id = req.getParameter("client_id");
String code = req.getParameter("code");
String redirect_uri = SqlOperat.queryOauthUser(client_id,"redirect_uri");
//校验url中OAuth用户名和密码及参数正确性
if (SqlOperat.queryOauthUser(client_id,"client_secret")
.equals(req.getParameter("client_secret"))){
//&& redirect_uri.equals(req.getParameter("redirect_uri"))
if ("authorization_code".equals(req.getParameter("grant_type"))) {
//获取客户端code ,校验code正确有效性
if (checkAccessCodeOk(code, client_id)) {
//发布token
String token = getJsonData(client_id);
resp.setContentType("application/json;charset=utf-8");
System.out.println(token);
resp.getWriter().write(token);
}else {
responseError(resp, 1000);
}
}else if("refresh_token".equals(req.getParameter("grant_type"))){
//验证refresh_token正确性
if (req.getParameter("refresh_token")
.equals(SqlOperat.queryLastOauthRefreshToken(client_id))){
String token = getJsonData(client_id);
resp.setContentType("application/json;charset=utf-8");
System.out.println(token);
resp.getWriter().write(token);
}
}else {responseError(resp, 2000);}
}else {
//返回错误响应
responseError(resp, 3000);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
/**
*
* @param client_id OAuth用户名
* @return token json
*/
private static String getJsonData(String client_id){
System.out.println("getJsonData-------");
//刷新access_token
String access_token = CodeFactory.getToken();
//刷新refresh_token
String refresh_token = CodeFactory.getToken();
//保存token记录
SqlOperat.saveAccessOrRefreshToken(access_token,true,client_id);
SqlOperat.saveAccessOrRefreshToken(refresh_token,false,client_id);
//token过期后进行刷新
String token = JSON.toJSONString(new TokenBean(access_token, 259200, refresh_token, null));
return token;
}
/**
* 返回错误响应
* @param resp s
*/
private static void responseError(HttpServletResponse resp, int errorCode) throws IOException {
resp.setContentType("application/json;charset=utf-8");
System.out.println("error!!");
//返回错误状态
resp.getWriter().write("{error\":"+errorCode+",\"error_description\":\"description}");
}
/**
* 校验code是否发放且正确未过期
* @param code code
* @param client_id oauth user
* @return code result?ok
*/
private static boolean checkAccessCodeOk(String code, String client_id){
String getCodeDate = SqlOperat.getAccessCode(code,client_id);
Date date = new Date();//获得系统时间.
String nowDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
if (getCodeDate != null){
long codeTime = Long.parseLong(SqlOperat.dateToStamp(getCodeDate));
long nowTime = Long.parseLong(SqlOperat.dateToStamp(nowDate));
System.out.println(codeTime+"--"+nowTime+"="+(nowTime-codeTime));
if (codeTime != 0 && (nowTime - codeTime)/1000 <= 60*2){
return true;
}else {
return false;
}
}else {
return false;
}
}
}
token&&Code生成:
/**
* @author uthor : willian fu
* @date : 2019-02-14.
*
*/
public class CodeFactory {
/**
* 获取随机code
* @return code
*/
public static String getCode(){
//生成长度40的随机code
String code = getRandomString(40);
return code;
}
/**
* 获取随机token
* @return token
*/
public static String getToken(){
//生成长度42的随机token
String token = getRandomString(42);
return token;
}
/**
* 字符串抽取随机
* @param count
* @return 随机数
*/
private static int getRandom(int count) {
return (int) Math.round(Math.random() * (count));
}
//字符串模板
private static String string = "abcdefghijk123456789lmnopqrstuvwxyz";
/**
* 生成随机字符串
* @param length
* @return 随机字符串
*/
private static String getRandomString(int length){
StringBuffer sb = new StringBuffer();
int len = string.length();
for (int i = 0; i < length; i++) {
sb.append(string.charAt(getRandom(len-1)));
}
return sb.toString();
}
@Test
public void getCodeTest(){
System.out.println(getCode());
}
}
token信息实体类:
/**
* @author : willian fu
* @date : 2019-02-14
*/
public class TokenBean {
//token值
String access_token;
//有效时间
Integer expires_in;
//新token
String refresh_token;
//权限范围
@JSONField(serialize=false)
String scope;
public TokenBean(String access_token, Integer expires_in, String refresh_token, String scope) {
this.access_token = access_token;
this.expires_in = expires_in;
this.refresh_token = refresh_token;
this.scope = scope;
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public Integer getExpires_in() {
return expires_in;
}
public void setExpires_in(Integer expires_in) {
this.expires_in = expires_in;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
数据库操作类实现:
/**
* @author : willian fu
* @date : 2019-02-14.
* 数据库交互
*/
public class SqlOperat {
/**
* 静态加载连接池
*/
private static JdbcTemplate jdbcTemplate = JdbcUtil.getJdbcTemplate();
/**
* 查询Oauth用户数据库密码
* @param oauthUser Oauth用户
* @param name 字段名
* @return password
*/
public static String queryOauthUser(String oauthUser, String name){
String sql = "select * from oauth_clients where client_id=?";
Map<String, Object> oauthUserMap = jdbcTemplate.queryForMap(sql, oauthUser);
return (String) oauthUserMap.get(name);
}
/**
* 查询最后发布的AccessToken
* @param oauthUser 用户
* @return lastAccessToken
*/
public static String queryLastOauthAccessToken(String oauthUser){
String sql = "SELECT * FROM oauth_access_tokens WHERE expires=\n" +
"(SELECT MAX(expires) FROM oauth_access_tokens WHERE client_id=?)";
Map<String, Object> oauthUserMap = jdbcTemplate.queryForMap(sql, oauthUser);
return (String) oauthUserMap.get("access_token");
}
/**
* 查询最后发布的RefreshToken
* @param oauthUser 用户
* @return RefreshToken
*/
public static String queryLastOauthRefreshToken(String oauthUser){
String sql = "SELECT * FROM oauth_refresh_tokens WHERE expires=" +
"(SELECT MAX(expires) FROM oauth_refresh_tokens WHERE client_id=?)";
Map<String, Object> oauthUserMap = jdbcTemplate.queryForMap(sql, oauthUser);
return (String) oauthUserMap.get("refresh_token");
}
/**
* 保存Token
* @param token token
* @param type true-Access / false-Refresh
* @return result
*/
public static int saveAccessOrRefreshToken(String token, boolean type, String client_id){
String sqlr = "INSERT INTO oauth_refresh_tokens(refresh_token,client_id,expires) VALUES(?,?,?)";
String sqla = "INSERT INTO oauth_access_tokens(access_token,client_id,expires) VALUES(?,?,?)";
int rows = 0;
Date date = new Date();//获得系统时间.
String nowTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);//将时间格式转换成符合Timestamp要求的格式.
Timestamp nowDate =Timestamp.valueOf(nowTime);//把时间转换
if (type){
rows = jdbcTemplate.update(sqla, token,client_id,nowDate);
}else {
rows = jdbcTemplate.update(sqlr, token,client_id,nowDate);
}
return rows;
}
/**
* 保存code授权码
* @param code AccessCode
* @param redirect_uri 回调地址
* @param client_id OauthUsername
* @return Sql result
*/
public static int savaAccessCode(String code, String redirect_uri, String client_id){
String sql = "INSERT INTO oauth_authorization_codes (authorization_code,client_id,redirect_uri,expires) VALUES (?,?,?,?)";
Date date = new Date();//获得系统时间
String nowTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);//将时间格式转换成符合Timestamp要求的格式.
Timestamp nowDate =Timestamp.valueOf(nowTime);//把时间转换
int rows = jdbcTemplate.update(sql,code,client_id,redirect_uri,nowDate);
return rows;
}
public static String getAccessCode(String code, String client_id){
String sql = "SELECT * FROM oauth_authorization_codes WHERE expires=" +
"(SELECT MAX(expires) FROM oauth_authorization_codes WHERE client_id=?)";
Map<String, Object> oauthUserMap = jdbcTemplate.queryForMap(sql, client_id);
if (code.equals(oauthUserMap.get("authorization_code"))){
return oauthUserMap.get("expires").toString();
}else {
return null;
}
}
/**
* yyyy-MM-dd HH:mm:ss 格式转时间戳
* @param s
* @return Stamp
* @throws ParseException
*/
public static String dateToStamp(String s){
String res;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
try {
date = simpleDateFormat.parse(s);
long ts = date.getTime();
res = String.valueOf(ts);
return res;
} catch (ParseException e) {
e.printStackTrace();
}
return "0";
}
/**
* 时间戳转时间
* @param s
* @return date
*/
public static String stampToDate(String s){
String res;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long lt = new Long(s);
Date date = new Date(lt);
res = simpleDateFormat.format(date);
return res;
}
}
记得导包。。。。。。。。。。。
欢迎留言和纠错