Commit 160c2fe1 by mahx

OAuth2 集成

parent 9cf7e21b
......@@ -52,6 +52,12 @@
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
</dependencies>
<build>
......@@ -92,4 +98,24 @@
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>jsqlparser-snapshots</id>
<snapshots>
<enabled>true</enabled>
</snapshots>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
<repository>
<id>nexus</id>
<url>http://139.198.127.28:18081/repository/maven-public/</url>
<name>keymobile</name>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
......@@ -4,14 +4,16 @@ import com.keymobile.authservice.component.SecurityConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@ComponentScan(basePackages = {"com.keymobile.sso",
"com.keymobile.config.logging", "com.keymobile.config.naming",
"com.keymobile.config.logging", "com.keymobile.config.naming", "com.keymobile.config.feignclient",
"com.keymobile.config.redisclient", "com.keymobile.authservice.component"}, excludeFilters = {
@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE, value= SecurityConfig.class)
})
......
......@@ -2,11 +2,33 @@ package com.keymobile.sso.api;
public class Constants {
public static final String Session_UserId = "userId";
public static final String Session_UserName = "userName";
public static final String Session_UserDName = "userDName";
public static final String Session_Roles = "roles";
public static final String Session_Lang = "lang";
public static final String SESSION_USER_ID = "userId";
public static final String SESSION_USER_NAME = "userName";
public static final String SESSION_USER_D_NAME = "userDName";
public static final String SESSION_ROLES = "roles";
public static final String SESSION_LANG = "lang";
public static final String JWT_ACCESS_TOKEN = "access_token";
public static final String JWT_TOKEN_TYPE = "Bearer";
public static final String JWT_ID_TOKEN = "id_token";
public static final String JWT_EXPIRES_IN = "expires_in";
public static final String OAUTH_AUTHORIZE_CODE_PARAM = "code";
public static final String OAUTH_AUTHORIZE_STATE_PARAM = "state";
public static final String OAUTH_AUTHORIZE_GRANT_TYPE_PARAM = "grant_type";
public static final String OAUTH_AUTHORIZE_CLIENT_ID_PARAM = "client_id";
public static final String OAUTH_AUTHORIZE_CLIENT_SECRET_PARAM = "client_secret";
public static final String OAUTH_AUTHORIZE_REDIRECT_URI_PARAM = "redirect_uri";
public static final String OAUTH_AUTHORIZE_RESPONSE_MODE = "query";
public static final String OAUTH_AUTHORIZE_STATE = "keymobile";
public static final String OAUTH_AUTHORIZE_RESPONSE_TYPE = "code";
public static final String OAUTH_AUTHORIZE_GRANT_TYPE = "authorization_code";
public static final String USER_INFO_NAME = "name";
public static final String USER_INFO_D_NAME = "dname";
public static final String USER_INFO_PASSWORD = "password";
}
package com.keymobile.sso.api;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.keymobile.authservice.component.CustomizedUserDetailService;
import com.keymobile.sso.oauth2.Oauth2Properties;
import com.keymobile.sso.service.AuthService;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.keymobile.sso.api.Constants.*;
@RestController
@RequestMapping(value = "/")
public class LoginManagement {
private static final Logger log = LoggerFactory.getLogger(LoginManagement.class);
@Autowired
private Oauth2Properties oauth2Properties;
@Autowired
private CustomizedUserDetailService customizedUserDetailService;
@Autowired
private AuthService authService;
private RestTemplate restTemplate;
@PostConstruct
public void init() {
restTemplate = new RestTemplateBuilder().basicAuthentication(oauth2Properties.getClientId(), oauth2Properties.getClientSecret()).build();
}
@RequestMapping(value = "/sessionInfo", method = {RequestMethod.POST, RequestMethod.GET})
public @ResponseBody Map<String,Object> verifyLogin(HttpServletRequest request, HttpServletResponse response) {
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Map<String,Object> rs = new HashMap<>();
String userNameWithIdAttached = userDetails.getUsername();
rs.put(Constants.Session_UserName, userNameWithIdAttached.split(":")[0]);
rs.put(Constants.Session_UserId, userNameWithIdAttached.split(":")[1]);
rs.put(Constants.Session_UserDName, userNameWithIdAttached.split(":")[2]);
rs.put(Constants.SESSION_USER_NAME, userNameWithIdAttached.split(":")[0]);
rs.put(Constants.SESSION_USER_ID, userNameWithIdAttached.split(":")[1]);
rs.put(Constants.SESSION_USER_D_NAME, userNameWithIdAttached.split(":")[2]);
List<String> roles = new ArrayList<>();
userDetails.getAuthorities().forEach(auth -> roles.add(auth.getAuthority()));
rs.put(Constants.Session_Roles, roles);
rs.put(Constants.SESSION_ROLES, roles);
HttpSession session = request.getSession();
Object lang = session.getAttribute(Constants.Session_Lang);
rs.put(Constants.Session_Lang, lang != null ? lang.toString() : "cn");
Object lang = session.getAttribute(Constants.SESSION_LANG);
Object access_token = session.getAttribute(Constants.JWT_ACCESS_TOKEN);
Object id_token = session.getAttribute(Constants.JWT_ID_TOKEN);
rs.put(Constants.JWT_ACCESS_TOKEN, access_token);
rs.put(Constants.JWT_ID_TOKEN, id_token);
rs.put(Constants.SESSION_LANG, lang != null ? lang.toString() : "cn");
return rs;
}
......@@ -38,11 +81,163 @@ public class LoginManagement {
public String setLANG(HttpServletRequest request, @RequestParam(value = "LANG", required = true) String LANG) {
HttpSession session = request.getSession();
if (!LANG.equals("en") && !LANG.equals("cn"))
session.setAttribute(Constants.Session_Lang, "cn");
session.setAttribute(Constants.SESSION_LANG, "cn");
else
session.setAttribute(Constants.Session_Lang, LANG);
session.setAttribute(Constants.SESSION_LANG, LANG);
return session.getAttribute(Constants.SESSION_LANG).toString();
}
@GetMapping("/oauth/login")
public void login(HttpServletResponse response) throws IOException {
String successAuthorizeUri = oauth2Properties.getAuthorizeFullUri();
log.info("LoginUri is {}", successAuthorizeUri);
response.sendRedirect(successAuthorizeUri);
}
@GetMapping("/oauth/logout")
public String logout(HttpServletRequest request) {
String loginOutUri = oauth2Properties.getAuthorizationLoginOutUri();
HttpSession session = request.getSession();
String idToken = "";
if (session != null) {
idToken = (String) session.getAttribute(Constants.JWT_ID_TOKEN);
idToken = idToken == null ? "" : idToken;
}
String postLogoutRedirectUri = oauth2Properties.getPostLogoutRedirectUri();
String fullLoginOutUri = String.format("%s?client_id=%s&id_token_hint=%s&post_logout_redirect_uri=%s",
loginOutUri, oauth2Properties.getClientId(), idToken, postLogoutRedirectUri);
log.info("LoginOutUri url is {}", fullLoginOutUri);
return fullLoginOutUri;
}
@GetMapping("/login")
public void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
String code = request.getParameter(Constants.OAUTH_AUTHORIZE_CODE_PARAM);
String state = request.getParameter(Constants.OAUTH_AUTHORIZE_STATE_PARAM);
log.info("回调携带参数----- code {} , state {} ", code, state);
Map<String, String> userDetailByTokenInfo = getUserDetailByTokenInfo(code, state, request);
if (CollectionUtils.isEmpty(userDetailByTokenInfo)) {
log.error("get user detail by token info failed, code {} , state {}", code, state);
throw new IllegalArgumentException("get user detail by token info failed, code " + code + " , state " + state);
}
List<Map<String, Object>> matchUser = null;
String uniqueName = userDetailByTokenInfo.get("sub");
try {
matchUser = authService.getUserByName(uniqueName);
} catch (Exception e) {
log.error("get user detail by name {} failed", uniqueName, e);
}
if (CollectionUtils.isEmpty(matchUser)) {
Map<String, Object> toAdd = new HashMap<>();
toAdd.put(USER_INFO_D_NAME, userDetailByTokenInfo.get("name"));
toAdd.put(USER_INFO_NAME, uniqueName);
toAdd.put(USER_INFO_PASSWORD, "37fa265330ad83eaa879efb1e2db6380896cf639");
authService.addUser(toAdd);
}
UserDetails userDetails = customizedUserDetailService.loadUserByUsername(uniqueName);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
String authorizationSuccessRedirectUri = oauth2Properties.getAuthorizationSuccessRedirectUri();
response.sendRedirect(authorizationSuccessRedirectUri);
}
private Map<String, String> getUserDetailByTokenInfo(String code, String state, HttpServletRequest request) {
try {
if (!StringUtils.isEmpty(code) && !StringUtils.isEmpty(state)) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add(OAUTH_AUTHORIZE_CODE_PARAM, code);
map.add(OAUTH_AUTHORIZE_CLIENT_ID_PARAM, oauth2Properties.getClientId());
map.add(OAUTH_AUTHORIZE_CLIENT_SECRET_PARAM, oauth2Properties.getClientSecret());
map.add(OAUTH_AUTHORIZE_REDIRECT_URI_PARAM, oauth2Properties.getPostLoginRedirectUri());
map.add(OAUTH_AUTHORIZE_GRANT_TYPE_PARAM, Constants.OAUTH_AUTHORIZE_GRANT_TYPE);
log.info("获取token的url is {}, 参数为 {}", oauth2Properties.getAccessTokenUri(), map);
Map<String, String> resp = restTemplate.postForObject(oauth2Properties.getAccessTokenUri(), map, Map.class);
if (null == resp || resp.isEmpty()) {
throw new RuntimeException("获取token失败!");
}
Object accessToken = resp.get("access_token");
Object idToken = resp.get("id_token");
Object expiresIn = resp.get("expires_in");
Object tokenTpye = resp.get("token_tpye");
HttpSession session = request.getSession();
session.setAttribute(Constants.JWT_ACCESS_TOKEN, accessToken);
session.setAttribute(Constants.JWT_ID_TOKEN, idToken);
session.setAttribute(Constants.JWT_EXPIRES_IN, expiresIn);
session.setAttribute(Constants.JWT_TOKEN_TYPE, tokenTpye);
log.info("获取到的 access_token is {}", accessToken);
log.info("获取到的 id_token is {}", idToken);
return getUserInfoFromUserInfoUri((String) accessToken);
}
throw new RuntimeException("获取token的参数code或者state为空!");
} catch (Exception e) {
throw new RuntimeException("获取token 错误!", e);
}
}
/**
* 使用userInfoUri端点获取用户信息
* @param accessToken 访问令牌
* @return 用户信息Map
*/
@SuppressWarnings("unchecked")
private Map<String, String> getUserInfoFromUserInfoUri(String accessToken) {
try {
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);
org.springframework.http.HttpEntity<String> entity = new org.springframework.http.HttpEntity<>(headers);
log.info("调用userInfoUri获取用户信息, url is {}", oauth2Properties.getUserInfoUri());
Map<String, String> userInfo = restTemplate.postForObject(
oauth2Properties.getUserInfoUri(),
entity,
Map.class
);
log.info("从userInfoUri获取到的用户信息为 {}", userInfo);
return userInfo;
} catch (Exception e) {
throw new RuntimeException("从userInfoUri获取用户信息错误!", e);
}
}
private Map<String, String> exactUserInfoFromToken(String idToken) {
try {
DecodedJWT decodedJWT = JWT.decode(idToken);
Map<String, Claim> claims = decodedJWT.getClaims();
Map<String, String> userInfoFromIdToken = new HashMap<>();
claims.forEach((key, claim) -> {
log.info("解析token获取到的参数名为 {}, 值为 {}", key, claim.asString());
userInfoFromIdToken.put(key, claim.asString());
});
String userName = userInfoFromIdToken.get("unique_name");
if (StringUtils.isEmpty(userName)) {
throw new RuntimeException("exactUserInfoFromToken error, 必要的用户名字段为空!");
} else {
// String unique_name = "MRCN\\80600745";
// String reallyName = unique_name.split("\\\\")[1];
String[] userNames = userName.split("\\\\");
if (userNames.length > 1) {
userInfoFromIdToken.put("unique_name", userNames[1]);
}
return session.getAttribute(Constants.Session_Lang).toString();
}
return userInfoFromIdToken;
} catch (Exception e) {
throw new RuntimeException("解析token出错!", e);
}
}
}
package com.keymobile.sso.conf;
import com.keymobile.sso.oauth2.Oauth2Properties;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
......@@ -11,10 +13,13 @@ import java.io.IOException;
@Component
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Autowired
private Oauth2Properties oauth2Properties;
@Override
public void commence(HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, AuthenticationException authException)
throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
response.sendRedirect(oauth2Properties.getAuthorizeFullUri());
}
}
package com.keymobile.sso.conf;
import com.keymobile.sso.oauth2.Oauth2Properties;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
......@@ -14,10 +17,44 @@ import java.io.IOException;
@Component
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private Oauth2Properties oauth2Properties;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpStatus.OK.value());
response.getWriter().flush();
clearAllCookies(request, response);
String logoutUri = oauth2Properties.getAuthorizationLoginOutUri();
String postLogoutRedirectUri = oauth2Properties.getPostLogoutRedirectUri();
if (logoutUri != null && !logoutUri.isEmpty()) {
StringBuilder redirectUrl = new StringBuilder(logoutUri);
if (postLogoutRedirectUri != null && !postLogoutRedirectUri.isEmpty()) {
redirectUrl.append(logoutUri.contains("?") ? "&" : "?");
redirectUrl.append("post_logout_redirect_uri=").append(java.net.URLEncoder.encode(postLogoutRedirectUri, "UTF-8"));
}
response.sendRedirect(redirectUrl.toString());
} else {
response.setStatus(HttpStatus.OK.value());
response.getWriter().flush();
}
}
private void clearAllCookies(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
cookie.setValue("");
cookie.setPath(getCookiePath(request));
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
}
private String getCookiePath(HttpServletRequest request) {
String contextPath = request.getContextPath();
return contextPath != null && !contextPath.isEmpty() ? contextPath : "/";
}
}
\ No newline at end of file
......@@ -38,7 +38,10 @@ public class SsoSecurityConfig {
@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((request) -> {
request.anyRequest().authenticated();
request
.requestMatchers("/login", "/signin", "/signout").permitAll()
.requestMatchers("/error").permitAll()
.anyRequest().authenticated();
});
http.csrf((httpSecurityCsrfConfigurer) -> {
httpSecurityCsrfConfigurer.disable();
......
package com.keymobile.sso.oauth2;
import com.keymobile.sso.api.Constants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "security.oauth2.client")
@Component
public class Oauth2Properties {
private String clientId;
private String clientSecret;
private String clientTokenUri;
private String userAuthorizationUri;
private String postLoginRedirectUri;
private String authorizationSuccessRedirectUri;
private String postLogoutRedirectUri;
private String accessTokenUri;
private String userInfoUri;
private String authorizationLoginOutUri;
public void setPostLogoutRedirectUri(String postLogoutRedirectUri) {
this.postLogoutRedirectUri = postLogoutRedirectUri;
}
public String getPostLogoutRedirectUri() {
return postLogoutRedirectUri;
}
public void setAuthorizationLoginOutUri(String authorizationLoginOutUri) {
this.authorizationLoginOutUri = authorizationLoginOutUri;
}
public String getAuthorizationLoginOutUri() {
return authorizationLoginOutUri;
}
public void setAccessTokenUri(String accessTokenUri) {
this.accessTokenUri = accessTokenUri;
}
public String getAccessTokenUri() {
return accessTokenUri;
}
public void setAuthorizationSuccessRedirectUri(String authorizationSuccessRedirectUri) {
this.authorizationSuccessRedirectUri = authorizationSuccessRedirectUri;
}
public String getAuthorizationSuccessRedirectUri() {
return authorizationSuccessRedirectUri;
}
public void setPostLoginRedirectUri(String postLoginRedirectUri) {
this.postLoginRedirectUri = postLoginRedirectUri;
}
public String getPostLoginRedirectUri() {
return postLoginRedirectUri;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getClientTokenUri() {
return clientTokenUri;
}
public void setClientTokenUri(String clientTokenUri) {
this.clientTokenUri = clientTokenUri;
}
public String getUserAuthorizationUri() {
return userAuthorizationUri;
}
public void setUserAuthorizationUri(String userAuthorizationUri) {
this.userAuthorizationUri = userAuthorizationUri;
}
public String getUserInfoUri() {
return userInfoUri;
}
public void setUserInfoUri(String userInfoUri) {
this.userInfoUri = userInfoUri;
}
public String getAuthorizeFullUri() {
String authorizeUri = getUserAuthorizationUri();
String cliId = getClientId();
String redirectUri = getPostLoginRedirectUri();
String responseType = Constants.OAUTH_AUTHORIZE_RESPONSE_TYPE;
String responseMode = Constants.OAUTH_AUTHORIZE_RESPONSE_MODE;
String state = Constants.OAUTH_AUTHORIZE_STATE;
String authorizeFullUri = String.format("%s?client_id=%s&redirect_uri=%s&response_type=%s&state=%s&response_model=%s",
authorizeUri, cliId, redirectUri, responseType, state, responseMode);
return authorizeFullUri;
}
}
package com.keymobile.sso.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@FeignClient(value = "authService")
public interface AuthService {
@RequestMapping(value = "/users/findByName")
List<Map<String, Object>> getUserByName(@RequestParam(value = "match") String match);
@PostMapping(value = "/users")
Map<String, Object> addUser(@RequestBody Map<String, Object> user);
}
......@@ -28,3 +28,16 @@ logging:
level:
root: info
config: classpath:logback-custom.xml
security:
oauth2:
client:
client-id: 3822273eeb1a432a9041221b67f82979
client-secret: 3ca5a9aeced9476dbe0ff8207b2363ca
access-token-uri: https://portal-udadmin.sznsmic.com/auth/ud/oidc/token
user-authorization-uri: https://portal-udadmin.sznsmic.com/auth/ud/oidc/authorize
user-info-uri: https://portal-udadmin.sznsmic.com/auth/ud/oidc/userinfo
authorization-success-redirect-uri: http://10.193.54.42/text2sql/
authorization-login-out-uri:
post-login-redirect-uri: http://10.193.54.42/api/auth/login
post-logout-redirect_uri: http://10.193.54.42/api/auth/signout
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment