公司项目需求,需要做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 才几月,好多地方不完善,有发现问题,请多多指教。