SSO-prictice-in-web

A few weeks ago, our team meet a problem that how multi-web-app share the information of user’s sign state. here is the situation:

there are 3 web systems, user can login in them with the same account, when a user login in the first system, he can access the the second or the third without loginning agian.

the key problem laies in how to share the loginning state. SSO is exactly designed for this situation. SSO is the short name for Single Sign On, which means that user need to sign on only once, and he can access any other relative site. it is the most popular solution for integrating multi-site. for example, when you access sina.com.cn with your account, you can access weibo.com,news.sina.com.cn and so on, you need to input your account only once.

anyway, the most important part we want to figure out is how SSO works, my understanding is as follows:

There must exist a SSO service for all site in the whole system, the service privid common entry for loginning,account api,the management of user's online state .

1.common entry for loginning

actually, the point for common laies in login action, you can design diffent loginning’page for diffent site, but the logic for loginning action must be the same, usually, login action contains these thing:

  • validate the account
  • allocate account’s token , store the token into cookies
  • if the valid account’s token exists in the cookies, there is no need to login , redirect to the site.

hear is my code for entry :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RequestMapping("index")
public ModelAndView index(
@RequestParam("appId") String appid, // site id
@RequestParam("to") String to, // site callback url
HttpServletRequest req, HttpServletResponse res, ModelMap data) throws Exception {
if (!ObjectId.isValid(appid)) {
throw new Exception("error appId");
}
ObjectId appId = new ObjectId(appid);
DBCollection coll = portalMongo.getAppCollection();
DBObject app = coll.findOne(new BasicDBObject("_id", appId));
if (app == null) {
throw new Exception("error appId, app doesn't exists!");
}

// if the valid account's token exists in the cookies, there is no need to login , redirect to the site.

String token = CookieUtils.findACookieToken(req);
if (token != null && verifyTokenValid(token)) {
try {
String url = buildRedirectUrl(to, token);
res.sendRedirect(url);
return null;
} catch (IOException e) {
e.printStackTrace();
}
}

data.put("app", app);
data.put("to", to);

return new ModelAndView("sso/index", "data", data);
}

here is the code for login action :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@RequestMapping(value = "doLogin", method = RequestMethod.POST)
@ResponseBody
public AjaxData doLogin(
@RequestParam("loginname") String loginname,
@RequestParam("password") String password,
@RequestParam("to") String to,
@RequestParam("appId") ObjectId appId,
HttpServletRequest req, HttpServletResponse resp) {
AjaxData data = new AjaxData();

DBCollection collAdminUser = weiposMongo.getAdminUserCollection();
DBObject admin = collAdminUser.findOne(new BasicDBObject("loginname", loginname));
if (admin == null) {
data.info = "用户不存在";
return data;
}
String passMd5 = MD5.getMD5(password.getBytes());
if (!passMd5.equals((String) admin.get("password"))) {
data.info = "密码不正确";
return data;
}

DBObject log = new BasicDBObject();
log.put("to", to);
log.put("loginAppId", appId);
log.put("loginTime", Calendar.getInstance().getTime());
log.put("status", 1);
log.put("accountId", admin.get("_id"));
DBCollection collSSOLog = portalMongo.getSSOLogCollection();
collSSOLog.insert(log);

// 写Cookie
String token = log.get("_id").toString();
CookieUtils.writeACookieToken(resp, token);
HashMap<String, Object> ret = new HashMap<String, Object>();
ret.put("token", token);
ret.put("to", to);

String url = buildRedirectUrl(to, token);
ret.put("url", url);

data.status = 1;
data.info = "登录成功!";
data.data = ret;

return data;
}

2. account api

when an user signs in from the common login entry, which means the SSO service allocated a account token to a site, and restored the token into the cookies. account api privid the the api to get account’s data by token. the account token is the unique key to comfirm the account. SSO service can handle all request for any site in the system by the account api, and the sites can share account’s data by this api.

here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@RequestMapping("getUserByToken")
@ResponseBody
public AjaxData getUserByToken(@RequestParam("token") String token, HttpServletRequest req, HttpServletResponse res, ModelMap data) {

AjaxData ad = new AjaxData();
if (!ObjectId.isValid(token)) {
ad.info = "Token无效";
return ad;
}

DBCollection collSSOLog = portalMongo.getSSOLogCollection();
DBObject log = collSSOLog.findOne(new BasicDBObject("_id", new ObjectId(token)));
if (log == null) {
ad.info = "Token无效";
return ad;
}
int status = (Integer) log.get("status");
if (status != 1) {
ad.info = "Token已失效";
return ad;
}
HashMap<String, Object> ret = new HashMap<String, Object>();
ret.put("token", token);

ObjectId accountId = (ObjectId) log.get("accountId");
DBCollection collAdminUser = weiposMongo.getAdminUserCollection();
DBObject admin = collAdminUser.findOne(new BasicDBObject("_id", accountId));
if (admin == null) {
ad.info = "Token数据异常";
return ad;
}
ret.put("accountId", admin.get("_id").toString());// 管理员ID
ret.put("name", admin.get("name"));


ad.data = ret;
ad.status = 1;
ad.info = "登录成功!";
return ad;
}

3. the management of user’s online state

online state is the most important part in the whole things. let’s clean these things up.

  • when an user is loginning, the login action handle the submit.
  • login action will allocate a token, and restored it to the browers’ cookies by response object.
  • any other request from browers will find the token from cookies, and then it can been thought online by SSO service, SSO service will redirect to the callback url .
  • when any site sign out, the SSO service will mark the token ,and the token will be expired.

as you see, it’s not that complicated, i give a demo show about this.
i preclaim two things:

  • sso service url

    http:192.168.1.119:8080/portal/

  • site url

    http:192.168.1.119:8080/pdemo

step 1. when i tap site url, the browers will redirect to the loginning page in the sso service.

step 2. when i input the account to sign in the sso service, the browers will take me back to the site in the step 1.

so , except for the site demo code, the SSO service is completed intruduced , hope that help you.