Резюме:
В этой статье в основном рассказывается об использовании oauth2 внутри компании, чтобы меня запомнили.Изначально этот цикл статей не собирался обновлять, но компании нужно было запомнить реализацию этой функции, поэтому мне пришлось потратить 2 дня на изучение исходный код. Специально для всех.
Реализация функции «Запомнить меня»
Пожалуйста, обратитесь кCommunity Spring Security от вводной до расширенной серии руководствизСедьмой анализ исходного кода Spring Security: Spring Security запомнить меня
вопрос:
Во-первых, в разработке люди используют ajax для передачи данных.ThymeleafТехнология может быть не такой распространенной.В процессе запоминания моей реализации функции будет выполняться процесс перехода страницы авторизации.Потому что в предыдущей статье я использовал стойку регистрации для генерации формы формы для автоматического перехода.Конкретный код следующим образом:
@RequestMapping({ "/oauth/my_approval"})
@ResponseBody
public JSONObject getAccessConfirmation(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
@SuppressWarnings("unchecked")
Map scopes = (Map) (model.containsKey("scopes") ? model.get("scopes") : request.getAttribute("scopes"));
List scopeList = new ArrayList<>();
for (String scope : scopes.keySet()) {
scopeList.add(scope);
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("scopeList",scopeList);
return jsonObject;
}
function auth(){
//定义一个form表单
var form=$("<form>");
$(document.body).append(form);
form.attr("method","post");
form.attr("action","../oauth/authorize");
var inputUser=$("<input>");
inputUser.attr("type","hidden");
inputUser.attr("name","user_oauth_approval");
inputUser.attr("value","true");
for(var i = 0;i<scopeList.length;i++){
var input = $("<input>");
input.attr("type","hidden");
input.attr("name",""+scopeList[i]+"")
input.attr("value","true");
form.append(input);
}
form.append(inputUser);
form.submit();//表单提交
}
Для получения дополнительной информации, пожалуйста, обратитесь к моему github, связанному сspring4allсвязанный код
Внимательные люди обнаружат, что этот переход вызван событием щелчка в предыдущем абзаце.Если это функция «запомнить меня», то она не будет автоматически логиниться через триггер события внешнего интерфейса, а затем приходить в это авторизованное место.В этом случае, мы не можем этого достичь./oauth/authorizeпроцесс сертификации. Исходный код ссылки выглядит следующим образом:
//..........省略相关代码
@FrameworkEndpoint
@SessionAttributes({"authorizationRequest"})
public class AuthorizationEndpoint extends AbstractEndpoint {
private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
private RedirectResolver redirectResolver = new DefaultRedirectResolver();
private UserApprovalHandler userApprovalHandler = new DefaultUserApprovalHandler();
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();
private String userApprovalPage = "forward:/oauth/confirm_access";
private String errorPage = "forward:/oauth/error";
private Object implicitLock = new Object();
public AuthorizationEndpoint() {
}
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
this.sessionAttributeStore = sessionAttributeStore;
}
public void setErrorPage(String errorPage) {
this.errorPage = errorPage;
}
@RequestMapping({"/oauth/authorize"})
public ModelAndView authorize(Map model, @RequestParam Map parameters, SessionStatus sessionStatus, Principal principal) {
AuthorizationRequest authorizationRequest = this.getOAuth2RequestFactory().createAuthorizationRequest(parameters);
Set responseTypes = authorizationRequest.getResponseTypes();
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
} else if (authorizationRequest.getClientId() == null) {
throw new InvalidClientException("A client id must be provided");
} else {
try {
if (principal instanceof Authentication && ((Authentication)principal).isAuthenticated()) {
ClientDetails client = this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
String redirectUriParameter = (String)authorizationRequest.getRequestParameters().get("redirect_uri");
String resolvedRedirect = this.redirectResolver.resolveRedirect(redirectUriParameter, client);
if (!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException("A redirectUri must be either supplied or preconfigured in the ClientDetails");
} else {
authorizationRequest.setRedirectUri(resolvedRedirect);
this.oauth2RequestValidator.validateScope(authorizationRequest, client);
authorizationRequest = this.userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication)principal);
boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
authorizationRequest.setApproved(approved);
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
return this.getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
return new ModelAndView(this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal));
}
}
model.put("authorizationRequest", authorizationRequest);
return this.getUserApprovalPageResponse(model, authorizationRequest, (Authentication)principal);
}
} else {
throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed.");
}
} catch (RuntimeException var11) {
sessionStatus.setComplete();
throw var11;
}
}
}
@RequestMapping(
value = {"/oauth/authorize"},
method = {RequestMethod.POST},
params = {"user_oauth_approval"}
)
public View approveOrDeny(@RequestParam Map approvalParameters, Map model, SessionStatus sessionStatus, Principal principal) {
if (!(principal instanceof Authentication)) {
sessionStatus.setComplete();
throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorizing an access token.");
} else {
AuthorizationRequest authorizationRequest = (AuthorizationRequest)model.get("authorizationRequest");
if (authorizationRequest == null) {
sessionStatus.setComplete();
throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
} else {
RedirectView var8;
try {
Set responseTypes = authorizationRequest.getResponseTypes();
authorizationRequest.setApprovalParameters(approvalParameters);
authorizationRequest = this.userApprovalHandler.updateAfterApproval(authorizationRequest, (Authentication)principal);
boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
authorizationRequest.setApproved(approved);
if (authorizationRequest.getRedirectUri() == null) {
sessionStatus.setComplete();
throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
}
if (authorizationRequest.isApproved()) {
View var12;
if (responseTypes.contains("token")) {
var12 = this.getImplicitGrantResponse(authorizationRequest).getView();
return var12;
}
var12 = this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal);
return var12;
}
var8 = new RedirectView(this.getUnsuccessfulRedirect(authorizationRequest, new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")), false, true, false);
} finally {
sessionStatus.setComplete();
}
return var8;
}
}
}
private ModelAndView getUserApprovalPageResponse(Map model, AuthorizationRequest authorizationRequest, Authentication principal) {
this.logger.debug("Loading user approval page: " + this.userApprovalPage);
model.putAll(this.userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal));
return new ModelAndView(this.userApprovalPage, model);
}
private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorizationRequest) {
try {
TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(authorizationRequest, "implicit");
OAuth2Request storedOAuth2Request = this.getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
OAuth2AccessToken accessToken = this.getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
if (accessToken == null) {
throw new UnsupportedResponseTypeException("Unsupported response type: token");
} else {
return new ModelAndView(new RedirectView(this.appendAccessToken(authorizationRequest, accessToken), false, true, false));
}
} catch (OAuth2Exception var5) {
return new ModelAndView(new RedirectView(this.getUnsuccessfulRedirect(authorizationRequest, var5, true), false, true, false));
}
}
//..........省略相关代码
private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) {
try {
return new RedirectView(this.getSuccessfulRedirect(authorizationRequest, this.generateCode(authorizationRequest, authUser)), false, true, false);
} catch (OAuth2Exception var4) {
return new RedirectView(this.getUnsuccessfulRedirect(authorizationRequest, var4, false), false, true, false);
}
}
//..........省略相关代码
private ModelAndView handleException(Exception e, ServletWebRequest webRequest) throws Exception {
ResponseEntity translate = this.getExceptionTranslator().translate(e);
webRequest.getResponse().setStatus(translate.getStatusCode().value());
if (!(e instanceof ClientAuthenticationException) && !(e instanceof RedirectMismatchException)) {
AuthorizationRequest authorizationRequest = null;
try {
authorizationRequest = this.getAuthorizationRequestForError(webRequest);
String requestedRedirectParam = (String)authorizationRequest.getRequestParameters().get("redirect_uri");
String requestedRedirect = this.redirectResolver.resolveRedirect(requestedRedirectParam, this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId()));
authorizationRequest.setRedirectUri(requestedRedirect);
String redirect = this.getUnsuccessfulRedirect(authorizationRequest, (OAuth2Exception)translate.getBody(), authorizationRequest.getResponseTypes().contains("token"));
return new ModelAndView(new RedirectView(redirect, false, true, false));
} catch (OAuth2Exception var8) {
return new ModelAndView(this.errorPage, Collections.singletonMap("error", translate.getBody()));
}
} else {
return new ModelAndView(this.errorPage, Collections.singletonMap("error", translate.getBody()));
}
}
//..........省略相关代码
}
Внутренне реализовано в исходном кодеModelAndViewЧтобы сделать прыжок, можно собственно настроить его отображение.Согласно этой идее, мы также можем реализоватьViewсделать прыжок
Процесс обработки
Мысль:Будь то обычный процесс входа в систему или процесс функции «Запомнить меня», переход непосредственно на страницуscopeа такжеuser_oauth_approvalПодождите, пока данные заполнятся, а затем отправьте их автоматически.
Во-первых, на стойке регистрации необходимо использовать отправку формы, иначе она не подействует. Это место меня надолго застряло. Если это ajax, то в функции ошибки будет возвращен код всей страницы, потому что возвращаемое представление — это то место, куда возвращается пользовательская страница авторизации.
Давайте посмотрим на конкретный код
@RequestMapping({ "/oauth/my_approval"})
public String getAccessConfirmation(Map model, HttpServletRequest request) throws Exception {
@SuppressWarnings("unchecked")
Map scopes = (Map) (model.containsKey("scopes") ? model.get("scopes") : request.getAttribute("scopes"));
List list = new ArrayList<>();
for (String scope : scopes.keySet()) {
list.add(scope);
}
model.put("scopes",list);
Cookie[] cookies = request.getCookies();
boolean bool = Arrays.stream(cookies).anyMatch(x->x.getName().equals("remember-me-cookie-name"));
Principal principal = request.getUserPrincipal();
String usernmae = principal.getName();
model.put("username",usernmae);
String check = bool==true ? "true" : "false";
model.put("remember",check);
return "approval";
}
Взгляните на мой конкретныйapproval.htmlПосмотреть
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
</head>
<body>
<form th:action="@{../oauth/authorize}" th:method="post">
<div th:each="scope:${scopes}">
<input th:type="hidden" th:name="${scope}" th:value="true"/>
</div>
<input th:type="hidden" th:name="user_oauth_approval" th:value="true"/>
</form>
<script src="../../js/jquery.min.js">
<script src="../../js/jquery.cookie.js">
<script th:inline="javascript">
window.onload=function(){
var username = [[${username}]];
var remember = [[${remember}]];
if(remember == "true"){
$.cookie('phone', username, { expires:7,path: '/' });
}else{
$.cookie('phone', username, { path: '/' });
}
document.forms[0].submit();
}
</script>
</body>
</html>
Обратите внимание на изменение файлов конфигурации, из-за ограничений синтаксиса тимелеафа вы можете запускать Baidu по определенным причинам, и я не буду здесь слишком много рассказывать. . . . . .
Когда вы доберетесь до этого места, многие люди могут подумать, что все кончено, но это еще не так.remember-me-cookie-name, когда вы снова войдете в систему, он снова выполнит процесс запоминания меня, что приведет к ошибке входа
Решить выход из системы
Сначала, конечно, я думал о очистке куки.Я пробовал это место в течение долгого времени без успеха, и был осмеян великим богом в нашей группе, который только говорил, но не практиковал (java это наука, и простые вещи встречаются разные ситуации. , это может быть нелегко лечить), я надеюсь, что некоторые великие боги смогут найти решение после прочтения этой статьиsping security oauth2Файл cookie, который запоминает мою функцию, и, наконец, рассказать о моем решении, которое похоже на предыдущую идею работы с токенами, Поскольку некоторая информация о пользователе сохраняется в файле cookie браузера, эти данные должны быть проверены с помощью данные, хранящиеся в базе данных. , так как файл cookie не может быть обработан, то центр передачи будет обрабатывать базу данных.
Взгляните на код для выхода и входа в систему
//....................
@FrameworkEndpoint
public class RevokeTokenEndpoint {
@Autowired
@Qualifier("consumerTokenServices")
ConsumerTokenServices consumerTokenServices;
private static final Logger logger = LoggerFactory.getLogger(RevokeTokenEndpoint.class);
@DeleteMapping("/oauth/exit")
@ResponseBody
public JSONObject revokeToken(String principal) {
//消除token
String access_token = JdbcOperateUtils.query(principal);
if (!access_token.equals("gzw")) {
if (consumerTokenServices.revokeToken(access_token)) {
logger.info("oauth2 logout success with principal: "+ principal);
//消除cookie的校验数据
JdbcOperateUtils.exit(principal);
return ResultUtil.toJSONString(ResultEnum.SUCCESS,principal);
}
}else {
logger.info("oauth2 logout fail with principal: "+ principal);
return ResultUtil.toJSONString(ResultEnum.FAIL,principal);
}
return ResultUtil.toJSONString(ResultEnum.UNKONW_ERROR,principal);
}
}
Код проверки данных для удаления файлов cookie выглядит следующим образом:
/**
* 查询用户是否是用记住我登录
* @param username
* @return
*/
public static void exit(String username) {
Connection connection = ConnectionUtils.getConn();
String sql1 = "SELECT series FROM persistent_logins WHERE username = ? limit 1";
String sql2 = "DELETE FROM persistent_logins WHERE username = ? ";
PreparedStatement preparedStatement1 = null;
PreparedStatement preparedStatement2 = null;
try {
preparedStatement1 = connection.prepareStatement(sql1);
preparedStatement1.setString(1, username);
ResultSet resultSet = preparedStatement1.executeQuery();
if (resultSet.next()){
preparedStatement2 = connection.prepareStatement(sql2);
preparedStatement2.setString(1, username);
preparedStatement2.execute();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
ConnectionUtils.releaseConnection(connection);
}
}
Визуализация базы данных
Наконец, прикрепите оператор создания таблицы
Создайте
persistent_loginsповерхностьcreate table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null);
Суммировать:
Я надеюсь, что эта статья может быть полезна вам в вашей повседневной работе, и я буду продолжать обновлять статью с соответствующими проблемами, возникающими в моей повседневной работе. В будущем я синхронизирую код с github, чтобы все могли учиться вместе, и я надеюсь, что каждый сможет высказать свое мнение.
Обратитесь к моему адресу github:GitHub.com/Exact Evidence/Судный день для вас…
Серия Spring Security Oauth2 (1)
Серия Spring Security Oauth2 (2)
Серия Spring Security Oauth2 (3)
Серия Spring Security Oauth2 (четыре)