公司项目需求,需要做SSO统一认证,开发完毕后,把相关思路整理下。
涉及相关OAuth2基础知识:
https://www.cnblogs.com/kaleidoscope/p/9507261.html
先上流程图:
主要流程:
1.client 去认证中心获取 Auth Code
2.Client 根据 Auth Code 去资源服务器获取 Token(项目认证中心和资源服务器都统一在一个APP了)
3.Client 向认证中心请求跳转到Client 端
4.认证中心验证 Token,并向目标Client 端发送写session 的请求
5.目标Client 收到写入Session请求后,向 资源服务器验证Token,写入session,然后跳转到具体页面
大概流程写完了,现在上代码。
一、认证中心代码:
LoginController.java
package demo.sso.controller; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import demo.sso.ResponseCodes; import demo.sso.entity.Client; import demo.sso.entity.User; import demo.sso.helpers.HttpRequestHelper; import demo.sso.helpers.ShiroSecurityHelper; import demo.sso.helpers.VerifyCodeHelper; import demo.sso.model.ChangePasswordModel; import demo.sso.model.JsonResult; import demo.sso.model.LoginModel; import demo.sso.server.ClientService; import demo.sso.server.OAuthService; import demo.sso.server.UserService; import demo.sso.shiro.token.PhoneCodeToken; import demo.sso.utils.ExceptionUtils; @Controller public class LoginController { @Autowired private HttpRequestHelper httpRequestHelper; @Autowired private ClientService clientService; @Autowired private OAuthService oAuthService; @Autowired private UserService userService; @Autowired private ShiroSecurityHelper shiroSecurityHelper; @Autowired private VerifyCodeHelper verifyCodeHelper; private static final Logger logger = LoggerFactory.getLogger(LoginController.class); private List<Client> getClients() { List<Client> clients = clientService.findAll(); List<Client> clientList = new ArrayList<Client>();; for(Client client :clients) { if(client.canUsed()) clientList.add(client); } return clientList; } @RequestMapping(value = { "/", "/index" }, method = RequestMethod.GET) public Object index(HttpServletRequest request,ModelMap model) { model.addAttribute("name",shiroSecurityHelper.getName()); model.addAttribute("clients", getClients()); return "index"; } @RequestMapping(value = { "/changePassword" }, method = RequestMethod.GET) public String changePassword() { return "changePassword"; } @RequestMapping(value = { "/changePassword" }, method = RequestMethod.POST) public Object changePassword(@ModelAttribute("SpringWeb")ChangePasswordModel data ,ModelMap model ,HttpServletRequest request) { if(!data.getNewPassword().equals(data.getConfirmPassword())) { ModelAndView mav=new ModelAndView("changePassword"); mav.addObject("error", "密码不一致。"); return mav; } if(!userService.changePassword(shiroSecurityHelper.getUserid(), data.getOldPassword(),data.getNewPassword())) { ModelAndView mav=new ModelAndView("changePassword"); mav.addObject("error", "密码修改失败!"); return mav; } else { return "redirect:/logout"; } } @RequestMapping(value = { "/login" }, method = RequestMethod.GET) public Object login(HttpServletRequest req){ if(shiroSecurityHelper.isAuthenticated()) { return "redirect:/"; } if(shiroSecurityHelper.isAuthenticated()) { ModelAndView mav=new ModelAndView("login"); mav.addObject("error", "您登录,请勿重复登录"); return mav; } String error=null; String exceptionClassName = (String)req.getAttribute("shiroLoginFailure"); if(UnknownAccountException.class.getName().equals(exceptionClassName)) { error = "账号不正确!"; } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) { error = "密码不正确!"; } else if(LockedAccountException.class.getName().equals(exceptionClassName)) { error = "账号被锁定!"; } else if(AuthenticationException.class.getName().equals(exceptionClassName)) { error = "登录失败!!"; } else if(exceptionClassName != null) { error = "登录失败:" + exceptionClassName; } logger.debug(exceptionClassName); ModelAndView mav=new ModelAndView("login"); mav.addObject("error", error); return mav; } @RequestMapping(value = { "/login" }, method = RequestMethod.POST) public Object login(@ModelAttribute("SpringWeb")LoginModel login ,BindingResult result ,ModelMap model ,HttpServletRequest request) { if(shiroSecurityHelper.isAuthenticated()) { return "redirect:/index"; } String error=null; try { Session session = shiroSecurityHelper.getSession(); if("phone".equals(login.getType())) { PhoneCodeToken token = new PhoneCodeToken(login.getUsername(),login.getPassword()); shiroSecurityHelper.login(token); } else { UsernamePasswordToken token=new UsernamePasswordToken(login.getUsername(),login.getPassword()); shiroSecurityHelper.login(token); } User user = userService.findByPhone(login.getUsername()); List navigationBar=userService.getNavigationBar(user.getUsername()); session.setAttribute("navibar", navigationBar); session.setAttribute("username", user.getUsername()); session.setAttribute("name", user.getName()); session.setAttribute("userid", user.getUserid()); session.setAttribute("email", user.getEmail()); session.setAttribute("phone", user.getPhone()); } catch(Exception e) { e.printStackTrace(); String exceptionClassName = e.getClass().getName(); if(UnknownAccountException.class.getName().equals(exceptionClassName)) { error = "账号不正确!"; } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) { error = "密码不正确!"; } else if(LockedAccountException.class.getName().equals(exceptionClassName)) { error = "账号被锁定!"; } else if(AuthenticationException.class.getName().equals(exceptionClassName)) { error = "登录失败!"; } else if(exceptionClassName != null) { error = "登录失败:" + exceptionClassName; } else { error = "登录失败!!"; } ModelAndView mav=new ModelAndView("login"); mav.addObject("error", error); return mav; } return "redirect:"+getReturnUrl(request,login); } @RequestMapping("/logout") public Object logout(HttpServletRequest request) { String accessToken = shiroSecurityHelper.getSessionAccessToken(); shiroSecurityHelper.logout(); if(accessToken!=null&& accessToken.length()>0) { String username =oAuthService.getUsernameByAccessToken(accessToken.toString()); oAuthService.removeAccessToken(accessToken.toString()); shiroSecurityHelper.kickOutUser(username); } System.out.println( "no token redirect:" +httpRequestHelper.getCobineUrl(request, "/login")); return "redirect:"+httpRequestHelper.getCobineUrl(request, "/login"); } @RequestMapping("/checkCode") public Object checkCode(HttpServletRequest req,String phone){ JsonResult result = new JsonResult(); try { String code = verifyCodeHelper.createVerifyCode(6); System.out.println(code); boolean flag = userService.updateCode(phone, code); if(!flag) { result.setCode(ResponseCodes.SMS_SEND_FIAL); } }catch(Exception e) { logger.error(ExceptionUtils.getMessage(e)); e.printStackTrace(); result.setCode(ResponseCodes.SYSTEM_ERROR); } return new ResponseEntity(result.toString(), HttpStatus.OK); } private String getReturnUrl(HttpServletRequest request,LoginModel login) { String returnUrl = login.getReturnUrl(); if(returnUrl!=null) return returnUrl; else return httpRequestHelper.getCobineUrl(request, "/"); } }
OAuth2Controller.java
package demo.sso.controller; import java.net.URI; import java.net.URISyntaxException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest; import org.apache.oltu.oauth2.as.request.OAuthTokenRequest; import org.apache.oltu.oauth2.as.response.OAuthASResponse; import org.apache.oltu.oauth2.common.OAuth; import org.apache.oltu.oauth2.common.error.OAuthError; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.apache.oltu.oauth2.common.message.types.ResponseType; import org.apache.oltu.oauth2.common.utils.OAuthUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import com.alibaba.fastjson.JSON; import demo.sso.server.ClientService; import demo.sso.server.OAuthService; import demo.sso.utils.ExceptionUtils; import demo.sso.ResponseCodes; import demo.sso.ResponseMessages; import demo.sso.entity.Client; import demo.sso.entity.Status; import demo.sso.helpers.HttpRequestHelper; import demo.sso.helpers.ShiroSecurityHelper; import demo.sso.mapper.ClientMapper; import demo.sso.model.JsonResult; @Controller @RequestMapping("/v1/oauth2") public class OAuth2Controller { @Autowired private ClientMapper clientMapper; @Autowired private HttpRequestHelper httpRequestHelper; @Autowired private OAuthService oAuthService; @Autowired private ClientService clientService; @Autowired private ShiroSecurityHelper shiroSecurityHelper; private static final Logger logger = LoggerFactory.getLogger(OAuth2Controller.class); /** * 服务端授权页面,获取授权 code * @param model * @param request * @return * @throws URISyntaxException * @throws OAuthSystemException */ @SuppressWarnings("unchecked") @RequestMapping(value = "/authorize") public Object authorize( Model model, HttpServletRequest request) throws URISyntaxException, OAuthSystemException { logger.debug("---------服务端端/authorize----------------------------------------------------------------------------------"); try { HttpHeaders headers = new HttpHeaders(); headers.set("Content-Type","application/json; charset=utf-8"); //构建OAuth 授权请求 OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); //检查传入的客户端id是否正确 if (!oAuthService.checkClientId(oauthRequest.getClientId())) { OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription(ResponseMessages.getMessage(ResponseCodes.INVALID_CLIENT_ID)) .buildJSONMessage(); return new ResponseEntity(response.getBody(), headers, HttpStatus.valueOf(response.getResponseStatus())); } //如果用户没有登录,跳转到登陆页面 if (!login(request)) {//登录失败时跳转到登陆页面 model.addAttribute("client", clientService.findByClientId(oauthRequest.getClientId())); return "oauth2login"; } String username=""; if ("get".equalsIgnoreCase(request.getMethod())) { username = shiroSecurityHelper.getUsername(); } else { username = request.getParameter("username"); //获取用户名 } //生成授权码 String authorizationCode = null; //responseType目前仅支持CODE,另外还有TOKEN String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); if (responseType.equals(ResponseType.CODE.toString())) { OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); authorizationCode = oauthIssuerImpl.authorizationCode(); oAuthService.addAuthCode(authorizationCode, username); } //进行OAuth响应构建 OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND); //设置授权码 builder.setCode(authorizationCode); logger.debug("authorizationCode="+authorizationCode); //得到到客户端重定向地址 String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); //构建响应 final OAuthResponse response = builder.location(redirectURI).buildQueryMessage(); //根据OAuthResponse返回ResponseEntity响应 headers = new HttpHeaders(); headers.set("Content-Type","application/json; charset=utf-8"); headers.setLocation(new URI(response.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus())); } catch (OAuthProblemException e) { logger.error(ExceptionUtils.getMessage(e)); e.printStackTrace(); //出错处理 String redirectUri = e.getRedirectUri(); if (OAuthUtils.isEmpty(redirectUri)) { //告诉客户端没有传入redirectUri直接报错 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/json; charset=utf-8"); Status status = new Status(); status.setCode(HttpStatus.NOT_FOUND.value()); status.setMsg(ResponseMessages.getMessage(ResponseCodes.INVALID_REDIRECT_URI)); return new ResponseEntity(JSON.toJSONString(status), headers, HttpStatus.NOT_FOUND); } //返回错误消息(如?error=) final OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND) .error(e).location(redirectUri).buildQueryMessage(); HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(response.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus())); } } /** * 服务端授权页面-登录验证 * @param request * @return */ private boolean login(HttpServletRequest request) { if ("get".equalsIgnoreCase(request.getMethod())) { if(!shiroSecurityHelper.isAuthenticated()) { request.setAttribute("error", "请先登录"); return false; } else { return true; } } String username = request.getParameter("username"); String password = request.getParameter("password"); if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { request.setAttribute("error", "登录失败:用户名或密码不能为空"); return false; } //用户名和密码都保存在model中 //基于shiro实现登录 //用户名和密码保存到token中 AuthenticationToken token= new UsernamePasswordToken(username,password); try { //如果正常登录,表示没有异常.登陆成功 shiroSecurityHelper.login(token); return true; } catch (Exception e) { logger.error(ExceptionUtils.getMessage(e)); //如果异常,表示登录失败,重新跳转到登录页面 e.printStackTrace(); request.setAttribute("error", "登录失败,服务器繁忙."); return false; } } /** * 根据code 获取 accessToken * @param request * @return * @throws URISyntaxException * @throws OAuthSystemException */ @RequestMapping("/accessToken") public HttpEntity token(HttpServletRequest request) throws URISyntaxException, OAuthSystemException { logger.debug("---------服务端端/accessToken----------------------------------------------------------------------------------"); HttpHeaders headers = new HttpHeaders(); headers.set("Content-Type","application/json; charset=utf-8"); try { //构建OAuth请求 OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request); //检查提交的客户端id是否正确 if (!oAuthService.checkClientId(oauthRequest.getClientId())) { OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription(ResponseMessages.getMessage(ResponseCodes.INVALID_CLIENT_ID)) .buildJSONMessage(); return new ResponseEntity(response.getBody(), headers, HttpStatus.valueOf(response.getResponseStatus())); } // 检查客户端安全KEY是否正确 if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) { OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT) .setErrorDescription(ResponseMessages.getMessage(ResponseCodes.INVALID_CLIENT_ID)) .buildJSONMessage(); return new ResponseEntity(response.getBody(), headers, HttpStatus.valueOf(response.getResponseStatus())); } String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE); // 检查验证类型,此处只检查AUTHORIZATION_CODE类型,其他的还有PASSWORD或REFRESH_TOKEN if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())) { if (!oAuthService.checkAuthCode(authCode)) { OAuthResponse response = OAuthASResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_GRANT) .setErrorDescription(ResponseMessages.getMessage(ResponseCodes.INVALID_AUTH_CODE)) .buildJSONMessage(); return new ResponseEntity(response.getBody(), headers, HttpStatus.valueOf(response.getResponseStatus())); } } //生成Access Token OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); final String accessToken = oauthIssuerImpl.accessToken(); oAuthService.addAccessToken(accessToken, oAuthService.getUsernameByAuthCode(authCode)); //生成OAuth响应 OAuthResponse response = OAuthASResponse .tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(accessToken) .setExpiresIn(String.valueOf(oAuthService.getExpireIn())) .buildJSONMessage(); oAuthService.removeAuthCode(authCode); //将最后的session信息写入到 session 中 shiroSecurityHelper.setSessionAccessToken(accessToken); logger.debug("accessToken="+accessToken); //根据OAuthResponse生成ResponseEntity return new ResponseEntity(response.getBody(), headers, HttpStatus.valueOf(response.getResponseStatus())); } catch (OAuthProblemException e) { logger.error(ExceptionUtils.getMessage(e)); //构建错误响应 OAuthResponse res = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e) .buildJSONMessage(); return new ResponseEntity(res.getBody(), headers, HttpStatus.valueOf(res.getResponseStatus())); } } /** * 验证accessToken * * @param accessToken * @return */ @RequestMapping(value = "/checkAccessToken", method = RequestMethod.POST) public ResponseEntity checkAccessToken(@RequestParam("accessToken") String accessToken) { logger.debug("---------服务端端/checkAccessToken----------------------------------------------------------------------------------"); boolean b = oAuthService.checkAccessToken(accessToken); return b ? new ResponseEntity(HttpStatus.valueOf(HttpServletResponse.SC_OK)) : new ResponseEntity(HttpStatus.valueOf(HttpServletResponse.SC_UNAUTHORIZED)); } @RequestMapping(value = "/serverRedirect") public String serverRedirect(HttpServletRequest request,String accessToken,String redirect) { logger.debug("---------服务端端/serverRedirect----------------------------------------------------------------------------------"); try { String hostStr = httpRequestHelper.getUrlContextPath(redirect); Client client = clientMapper.findByClientHost(hostStr); if(client==null) { //#TODO client 查找失败 return "找不到客户端信息"; } logger.debug("redirect:"+client.getWriteSessionUri()+"?accessToken="+accessToken+"&redirect="+redirect); return "redirect:"+client.getWriteSessionUri()+"?accessToken="+accessToken+"&redirect="+redirect; } catch (Exception e) { logger.error(ExceptionUtils.getMessage(e)); // TODO 自动生成的 catch 块 e.printStackTrace(); } return null; } @RequestMapping(value = "/logout") public Object logout(HttpServletRequest request,@RequestParam("access_token") String accessToken) { JsonResult result = new JsonResult(); try { //获取用户名 String username = oAuthService.getUsernameByAccessToken(accessToken); if(username!=null && username.length()>0) { oAuthService.removeAccessToken(accessToken.toString()); shiroSecurityHelper.kickOutUser(username); } }catch(Exception e) { logger.error(ExceptionUtils.getMessage(e)); e.printStackTrace(); result.setCode(ResponseCodes.SYSTEM_ERROR); } return new ResponseEntity(result.toString(), HttpStatus.OK); } }
OAuth2ApiController.java
package demo.sso.controller; import javax.servlet.http.HttpServletRequest; import org.apache.oltu.oauth2.common.message.types.ParameterStyle; import org.apache.oltu.oauth2.rs.request.OAuthAccessResourceRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import demo.sso.ResponseCodes; import demo.sso.model.JsonResult; import demo.sso.server.OAuthService; import demo.sso.utils.ExceptionUtils; @Controller @RequestMapping("/v1/oauth2/api") public class OAuth2ApiController { @Autowired private OAuthService oAuthService; private static final Logger logger = LoggerFactory.getLogger(OAuth2ApiController.class); @RequestMapping(value = "/getUsername") public Object getUsername(HttpServletRequest request) { JsonResult result = new JsonResult(); try { //构建OAuth资源请求 OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.QUERY); //获取Access Token String accessToken = oauthRequest.getAccessToken(); //获取用户名 String username = oAuthService.getUsernameByAccessToken(accessToken); if(username==null) { result.setCode(ResponseCodes.INVALID_ACCESS_TOKEN); logger.debug(accessToken+" 找不到对应的用户"); } else { result.setData(username); } }catch(Exception e) { logger.error(ExceptionUtils.getMessage(e)); result.setCode(ResponseCodes.SYSTEM_ERROR); } return new ResponseEntity(result.toString(), HttpStatus.OK); } }
二、Client端代码
LoginController.java
package wo.hut.user.web; import javax.naming.AuthenticationException; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import wo.hut.user.helpers.HttpRequestHelper; import wo.hut.user.helpers.ShiroSecurityHelper; import wo.hut.user.service.UserService; @Controller public class LoginController { @Autowired private HttpRequestHelper httpRequestHelper; @Autowired private UserService userService; @Autowired private ShiroSecurityHelper shiroSecurityHelper; @RequestMapping("/login") public Object login(HttpServletRequest req){ if(shiroSecurityHelper.isAuthenticated()) { return "redirect:/"; } if(shiroSecurityHelper.isAuthenticated()) { ModelAndView mav=new ModelAndView("login"); mav.addObject("error", "您登录,请勿重复登录"); return mav; } String error=null; String exceptionClassName = (String)req.getAttribute("shiroLoginFailure"); if(UnknownAccountException.class.getName().equals(exceptionClassName)) { error = "账号不正确!"; } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) { error = "密码不正确!"; } else if(LockedAccountException.class.getName().equals(exceptionClassName)) { error = "账号被锁定!"; } else if(AuthenticationException.class.getName().equals(exceptionClassName)) { error = "登录失败!!"; } else if(exceptionClassName != null) { error = "登录失败:" + exceptionClassName; } ModelAndView mav=new ModelAndView("login"); mav.addObject("error", error); return mav; } @RequestMapping("/logout") public String logout(HttpServletRequest request,String redirect,ModelMap model) { System.out.println("---------客户端/logout----------------------------------------------------------------------------------"); if(redirect==null || redirect.length()<=0) { redirect = "/"; } shiroSecurityHelper.logout(); return "redirect:"+redirect; } }
OAuth2Controller.java
package wo.hut.user.web; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.oltu.oauth2.client.OAuthClient; import org.apache.oltu.oauth2.client.URLConnectionClient; import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest; import org.apache.oltu.oauth2.client.request.OAuthClientRequest; import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse; import org.apache.oltu.oauth2.client.response.OAuthResourceResponse; import org.apache.oltu.oauth2.common.OAuth; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.apache.oltu.oauth2.common.message.types.ResponseType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import wo.hut.common.utils.ExceptionUtils; import wo.hut.user.entity.OAuth2User; import wo.hut.user.helpers.HttpRequestHelper; import wo.hut.user.helpers.ShiroSecurityHelper; import wo.hut.user.helpers.VerifyCodeHelper; import wo.hut.user.model.JsonResult; import wo.hut.user.service.UserService; import wo.hut.user.shiro.token.CustomToken; @Controller @RequestMapping("/v1/oauth2") public class OAuth2Controller { @Value("${oauth2.CLIENT_ID}") private String CLIENT_ID = "3440CE11-D067-411D-90D0-AA60B88D4A74"; // 应用id CLIENT_ID @Value("${oauth2.CLIENT_SECRET}") private String CLIENT_SECRET = "03D609C8-ECFC-4E4A-A0CA-87AD0CBD01A4"; // 应用secret CLIENT_SECRET private String OAUTH_CLIENT_SESSION_URI = "/v1/oauth2/writeSession"; // 客户端写入 session 地址 private String OAUTH_CLIENT_REDIRECT_URI = "/v1/oauth2/serverRedirectCallbackCode";//客户端跳转回调地址 private String OAUTH_CLIENT_AUTHOIAZE_URI = "/v1/oauth2/authorize"; // 客户端用户授权 @Value("${oauth2.OAUTH_SERVICE_API}") private String OAUTH_SERVICE_API = "http://localhost:8180/demo.sso/v1/oauth2/api/getUsername"; // 测试开放数据api @Value("${oauth2.OAUTH_SERVICE_LOGOUT}") private String OAUTH_SERVICE_LOGOUT = "http://localhost:8180/demo.sso/v1/oauth2/logout"; // 测试开放数据api @Value("${oauth2.OAUTH_SERVICE_REDIRECT_SESSION_URI}") private String OAUTH_SERVICE_REDIRECT_SESSION_URI = "http://localhost:8180/demo.sso/v1/oauth2/serverRedirect"; // 服务端 session 写入中转地址 @Value("${oauth2.OAUTH_SERVER_TOKEN_URL}") private String OAUTH_SERVER_TOKEN_URL = "http://localhost:8180/demo.sso/v1/oauth2/accessToken"; // ACCESS_TOKEN获取地址 @Value("${oauth2.OAUTH_SERVER_URL}") private String OAUTH_SERVER_URL = "http://localhost:8180/demo.sso/v1/oauth2/authorize"; // 服务端授权地址 @Value("${oauth2.OAUTH_SERVER_CHECK_ACCESS_CODE_URL}") private String OAUTH_SERVER_CHECK_ACCESS_CODE_URL = "http://localhost:8180/demo.sso/v1/oauth2/checkAccessToken";//验证token是否有效 @Autowired private HttpRequestHelper httpRequestHelper; @Autowired private VerifyCodeHelper verifyCodeHelper; @Autowired private UserService userService; @Autowired private ShiroSecurityHelper shiroSecurityHelper; private static final Logger logger = LoggerFactory.getLogger(OAuth2Controller.class); @RequestMapping("/logout") public String logout(HttpServletRequest request,String redirect,ModelMap model) { logger.debug("---------客户端/logout----------------------------------------------------------------------------------"); if(redirect==null || redirect.length()<=0) { redirect = httpRequestHelper.getCobineUrl(request, "/login"); } String accessToken=shiroSecurityHelper.getAccessToken(); shiroSecurityHelper.logout(); if(accessToken==null || accessToken.length()==0) { logger.debug("没有 accessToken ,直接退出。"+redirect); return "redirect:"+redirect; } OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); try { logger.debug(OAUTH_SERVICE_LOGOUT); OAuthClientRequest userInfoRequest = new OAuthBearerClientRequest(OAUTH_SERVICE_LOGOUT) .setAccessToken(accessToken).buildQueryMessage(); OAuthResourceResponse resourceResponse = oAuthClient.resource(userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class); String jsonStr = resourceResponse.getBody(); JsonResult result = JSON.parseObject(jsonStr, new TypeReference<JsonResult>() {}); if(result==null ||!result.success()) { //#TODO 获取 oauth 服务器资源失败,跳转到提示页面 logger.debug( jsonStr+" 向 server 退出登录失败" ); model.addAttribute("message", "向 server 退出登录失败"); } }catch(Exception ex) { logger.error(ExceptionUtils.getMessage(ex)); ex.printStackTrace(); model.addAttribute("message", ex.toString()); } return "redirect:"+redirect; } /** * 客户端跳转中转地址 * @param request * @param response * @param attr * @param redirect * @return * @throws OAuthProblemException */ @RequestMapping("/serverRedirect") public String serverRedirect(HttpServletRequest request, HttpServletResponse response, RedirectAttributes attr ,String redirect) throws OAuthProblemException { logger.debug("---------客户端/serverRedirect----------------------------------------------------------------------------------"); //OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); String redirectUri=httpRequestHelper.getCobineUrl(request, OAUTH_CLIENT_REDIRECT_URI+"?redirect="+redirect); String requestUrl = null; try { //存在 accessToken ,则直接跳转到 sso 重定向页面 String accessToken = shiroSecurityHelper.getAccessToken(); if(accessToken!=null) { URL checkAccessTokenUrl = new URL(OAUTH_SERVER_CHECK_ACCESS_CODE_URL+"?accessToken=" + accessToken); logger.debug(checkAccessTokenUrl.toString()); HttpURLConnection conn = (HttpURLConnection) checkAccessTokenUrl.openConnection(); conn.setRequestMethod("POST"); conn.disconnect(); if(HttpServletResponse.SC_OK == conn.getResponseCode()) { String url=OAUTH_SERVICE_REDIRECT_SESSION_URI+"?accessToken="+accessToken+"&redirect="+URLEncoder.encode( redirect); logger.debug("redirect:"+url); return "redirect:"+url; } } else { shiroSecurityHelper.removeAccessToken(); } logger.debug(OAUTH_SERVER_URL); //构建oauthd的请求。设置请求服务地址(accessTokenUrl)、clientId、response_type、redirectUrl OAuthClientRequest accessTokenRequest = OAuthClientRequest .authorizationLocation(OAUTH_SERVER_URL) .setResponseType(ResponseType.CODE.toString()) .setClientId(CLIENT_ID) .setRedirectURI(redirectUri) .buildQueryMessage(); requestUrl = accessTokenRequest.getLocationUri(); } catch (Exception e) { e.printStackTrace(); logger.error(ExceptionUtils.getMessage(e)); } logger.debug("redirect:"+requestUrl ); return "redirect:"+requestUrl ; } /** * 客户端跳转回调验证地址 */ @RequestMapping("/serverRedirectCallbackCode") public Object serverRedirectCallbackCode(HttpServletRequest request, HttpServletResponse response, RedirectAttributes attr ,String redirect) throws OAuthProblemException { logger.debug("---------客户端/serverRedirectCallbackCode----------------------------------------------------------------------------------"); HttpServletRequest httpRequest = (HttpServletRequest) request; String code = httpRequest.getParameter("code"); System.out.println(code); OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); try { logger.debug(OAUTH_SERVER_TOKEN_URL); OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(OAUTH_SERVER_TOKEN_URL) .setGrantType(GrantType.AUTHORIZATION_CODE) .setClientId(CLIENT_ID) .setClientSecret(CLIENT_SECRET) .setCode(code) .setRedirectURI(OAUTH_SERVER_TOKEN_URL) .buildQueryMessage(); //去服务端请求access token,并返回响应 OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST); //获取服务端返回过来的access token String accessToken = oAuthResponse.getAccessToken(); //查看access token是否过期 //Long expiresIn = oAuthResponse.getExpiresIn(); shiroSecurityHelper.setAccessToken(accessToken); String url="redirect:"+OAUTH_SERVICE_REDIRECT_SESSION_URI+"?accessToken="+accessToken+"&redirect="+URLEncoder.encode( redirect); logger.debug(url); return url; } catch (OAuthSystemException e) { e.printStackTrace(); logger.error(ExceptionUtils.getMessage(e)); } return new ResponseEntity("sso登录失败!", HttpStatus.OK); } /** * sso 向客户端写入session 地址 * @param request * @param accessToken * @param redirect * @return */ @RequestMapping("/writeSession") public Object writeSession(HttpServletRequest request,HttpServletResponse response,String accessToken,String redirect) { logger.debug("---------客户端/writeSession----------------------------------------------------------------------------------"); if(shiroSecurityHelper.isAuthenticated()) { shiroSecurityHelper.setAccessToken(accessToken); logger.debug( "已经登录,redirect:"+redirect ); return "redirect:"+redirect ; } OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); try { logger.debug(OAUTH_SERVICE_API); OAuthClientRequest userInfoRequest = new OAuthBearerClientRequest(OAUTH_SERVICE_API) .setAccessToken(accessToken).buildQueryMessage(); OAuthResourceResponse resourceResponse = oAuthClient.resource(userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class); String jsonStr = resourceResponse.getBody(); JsonResult result = JSON.parseObject(jsonStr, new TypeReference<JsonResult>() {}); if(result==null || !result.success()) { //#TODO 获取 oauth 服务器资源失败,跳转到提示页面 logger.info( jsonStr+" json 转换失败,redirect:"+redirect ); return "redirect:"+redirect ; } String ssoUsername = result.getData().toString(); //根据SSO 的 username 查找对应的用户信息,查询不到表示 sso 和 wo+用户未做关联 OAuth2User user = userService.findBySsoUsername(ssoUsername); //创建关联关系 if (user==null) { //#TODO 客户端检查 UserMapping 信息失败,跳转到提示页面 logger.warn(ssoUsername+"---------------user mapping can not find."); String url="redirect:"+httpRequestHelper.getCobineUrl(request, OAUTH_CLIENT_AUTHOIAZE_URI)+"?accessToken="+accessToken+"&redirect="+URLEncoder.encode( redirect); logger.debug(url); return url; } //写入登录session CustomToken token = new CustomToken(user.getUsername()); shiroSecurityHelper.login(token); shiroSecurityHelper.setLoginInfo(user); shiroSecurityHelper.setAccessToken(accessToken); logger.debug( "redirect:"+redirect ); return "redirect:"+redirect ; } catch (Exception e) { e.printStackTrace(); logger.error(ExceptionUtils.getMessage(e)); } return new ResponseEntity("sso登录失败!", HttpStatus.OK); } /** * 客户端授权用户接入到sso页面,需要验证登录 * @param request * @param accessToken * @param redirect * @param model * @return */ @RequestMapping("/authorize") public Object authorize(HttpServletRequest request,String accessToken,String redirect,ModelMap model) { logger.debug("---------客户端/authorize----------------------------------------------------------------------------------"); if(!isLogin(request)) { try { String url=httpRequestHelper.getCobineUrl(request,"/login?returnUrl="+URLEncoder.encode(httpRequestHelper.getRequestUrl(request), "UTF-8")); return "redirect:"+url; }catch(Exception e) { return null; } } if ("get".equalsIgnoreCase(request.getMethod())) { model.addAttribute("message", "是否允许用户 "+shiroSecurityHelper.getUsername()+" 关联到【OAuth2 认证中心】?"); return "v1/oauth2/authorize"; } else { OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); try { HttpSession session = request.getSession(); logger.debug(OAUTH_SERVICE_API); OAuthClientRequest userInfoRequest = new OAuthBearerClientRequest(OAUTH_SERVICE_API) .setAccessToken(accessToken).buildQueryMessage(); OAuthResourceResponse resourceResponse = oAuthClient.resource(userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class); String jsonStr = resourceResponse.getBody(); JsonResult result = JSON.parseObject(jsonStr, new TypeReference<JsonResult>() {}); if(result==null ||!result.success()) { //#TODO 获取 oauth 服务器资源失败,跳转到提示页面 logger.debug( jsonStr+" 向 server 获取用户信息失败" ); model.addAttribute("message", "向 server 获取用户信息失败"); return "authorization/authorize"; } String ssoUsername = result.getData().toString(); String username = shiroSecurityHelper.getUsername(); boolean flag = userService.insertMapping(username, ssoUsername); if(!flag) { logger.debug( jsonStr+" 插入 oauth_mapping 失败" ); System.out.println( jsonStr+" 插入 oauth_mapping 失败" ); } }catch(Exception ex) { model.addAttribute("message", ex.toString()); return "v1/oauth2/authorize"; } logger.debug("redirect:"+httpRequestHelper.getCobineUrl(request, OAUTH_CLIENT_SESSION_URI)+"?accessToken="+accessToken+"&redirect="+URLEncoder.encode( redirect)); return "redirect:"+httpRequestHelper.getCobineUrl(request, OAUTH_CLIENT_SESSION_URI)+"?accessToken="+accessToken+"&redirect="+URLEncoder.encode( redirect); } } /** * 检查是否登录 * @param request * @return */ private boolean isLogin(HttpServletRequest request) { return shiroSecurityHelper.isAuthenticated(); } }
ClientService.java
package demo.sso.server; import java.util.List; import demo.sso.entity.Client; import demo.sso.entity.User; public interface ClientService { Client createClient(Client client); Client updateClient(Client client); void deleteClient(Long clientId); void logicalDeleteClient(Long clientId); List<Client> findAll(); Client findById(Long id); Client findByClientId(String clientId); Client findByClientSecret(String clientSecret); boolean canCreateClient(Client client); boolean canUpdateClient(Client client); }
OAuthService.java
package demo.sso.server; public interface OAuthService { //添加 auth code void addAuthCode(String authCode, String username); void removeAuthCode(String authCode); //添加 access token void addAccessToken(String accessToken, String username); void removeAccessToken(String accessToken); //验证auth code是否有效 boolean checkAuthCode(String authCode); //验证access token是否有效 boolean checkAccessToken(String accessToken); String getUsernameByAuthCode(String authCode); String getUsernameByAccessToken(String accessToken); //auth code / access token 过期时间 long getExpireIn(); boolean checkClientId(String clientId); boolean checkClientSecret(String clientSecret); }
PermissionService.java
package demo.sso.server; import java.util.List; import demo.sso.entity.Permission; public interface PermissionService { Long addPermission(Permission permission); void deletePermission(Long permissionId); void deleteMorePermissions(Long...permIds); Permission findById(Long permId); List<Permission> getPermissionsByRoleId(Long roleId); List<Permission> getAllPermissions(); void updatePermission(Permission permission); }
RoleService.java
package demo.sso.server; import java.util.List; import demo.sso.entity.Role; public interface RoleService { Long addRole(Role role,Long...permissionIds); void deleteRole(Long roleId); void deleteMoreRoles(Long...roleIds); Role getRoleById(Long roleId); List<Role> getRolesByUserName(String userName); List<Role> getAllRoles(); void updateRole(Role role,Long...permIds); void addRolePermissions(Long roleId,Long...permissionIds); }
UserService.java
package demo.sso.server; import java.util.List; import java.util.Set; import demo.sso.entity.User; import demo.sso.model.AdminUserSearchModel; import demo.sso.model.Navigation; import demo.sso.model.PageInfo; public interface UserService { boolean canCreateUser(User user); User createUser(User user); boolean canUpdateUser(User user); boolean updateUser(User user); void deleteUser(Long userId); void logicalDeleteUser(Long userId); void changePassword(Long userId, String newPassword); boolean changePassword(Long userId,String oldPassword,String newPassword); List<User> findAll(); PageInfo<User> adminUserSearchList(AdminUserSearchModel search); User findByUsername(String username); User findByPhone(String phone); User findById(Long id); Set<String> findRolesByUserName(String userName); Set<String> findPermissionsByUserName(String userName); List<Navigation> getNavigationBar(String userName); boolean updateCode(String phone,String code); }
具体DB层代码,就不上了。
转Java 才几月,好多地方不完善,有发现问题,请多多指教。