Commit 3783fe52 by lanmw

update,注销功能

parent 8c8303b2
...@@ -79,8 +79,14 @@ ...@@ -79,8 +79,14 @@
</dependency> </dependency>
<!--引入oauth2--> <!--引入oauth2-->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId> <artifactId>jackson-core</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency> </dependency>
<!--引入oauth2--> <!--引入oauth2-->
<dependency> <dependency>
...@@ -190,44 +196,6 @@ ...@@ -190,44 +196,6 @@
<version>1.11</version> <version>1.11</version>
</dependency> </dependency>
<!--引入saml-->
<dependency>
<groupId>org.springframework.security.extensions</groupId>
<artifactId>spring-security-saml2-core</artifactId>
<version>1.0.9.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.opensaml</groupId>
<artifactId>opensaml</artifactId>
</exclusion>
<exclusion>
<artifactId>not-going-to-be-commons-ssl</artifactId>
<groupId>com.narupley</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml</artifactId>
<version>2.6.4</version>
<exclusions>
<exclusion>
<artifactId>not-yet-commons-ssl</artifactId>
<groupId>ca.juliusdavies</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入saml-->
<!-- https://mvnrepository.com/artifact/org.apache.servicemix.bundles/org.apache.servicemix.bundles.not-yet-commons-ssl --> <!-- https://mvnrepository.com/artifact/org.apache.servicemix.bundles/org.apache.servicemix.bundles.not-yet-commons-ssl -->
<dependency> <dependency>
<groupId>org.apache.servicemix.bundles</groupId> <groupId>org.apache.servicemix.bundles</groupId>
......
...@@ -3,11 +3,13 @@ package com.keymobile.login; ...@@ -3,11 +3,13 @@ package com.keymobile.login;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication @SpringBootApplication
@EnableDiscoveryClient @EnableDiscoveryClient
@ComponentScan(basePackages = {"com.keymobile.login", "com.keymobile.config.logging", "com.keymobile.login.saml"}) @EnableFeignClients
@ComponentScan(basePackages = {"com.keymobile.login", "com.keymobile.config.logging", "com.keymobile.login.service"})
public class LoginApplication { public class LoginApplication {
public static void main(String[] args) { public static void main(String[] args) {
......
...@@ -7,6 +7,21 @@ public class Constants { ...@@ -7,6 +7,21 @@ public class Constants {
public static final String Session_UserDName = "userDName"; public static final String Session_UserDName = "userDName";
public static final String Session_Roles = "roles"; public static final String Session_Roles = "roles";
public static final String Session_Lang = "lang"; 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_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";
} }
package com.keymobile.login.api; package com.keymobile.login.api;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.keymobile.auth.common.security.CustomizedUserDetailService;
import com.keymobile.login.oauth2.Oauth2Properties;
import com.keymobile.login.service.AuthService;
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.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
...@@ -17,20 +35,28 @@ import java.util.Map; ...@@ -17,20 +35,28 @@ import java.util.Map;
@RequestMapping(value = "/") @RequestMapping(value = "/")
public class LoginManagement { public class LoginManagement {
@RequestMapping(value = "sessionInfo", method = {RequestMethod.POST, RequestMethod.GET}) private static final Logger log = LoggerFactory.getLogger(LoginManagement.class);
public @ResponseBody
Object verifyLogin(HttpServletRequest request, HttpServletResponse response) {
// Map<String, Object> map = new HashMap<>(); @Autowired
// map.put("context", SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString()); private Oauth2Properties oauth2Properties;
// return map;
UserDetails userDetails = null; @Autowired
try { private CustomizedUserDetailService customizedUserDetailService;
userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) { @Autowired
return "redirect: http://localhost:1111/oauth/authorize?client_id=javaboy&redirect_uri=http://localhost:8089/api/auth/token&response_type=code&state=3UJF8q"; private AuthService authService;
private RestTemplate restTemplate;
@PostConstruct
public void init() {
restTemplate = new RestTemplateBuilder().basicAuthorization(oauth2Properties.getClientId(), oauth2Properties.getClientSecret()).build();
} }
@RequestMapping(value = "sessionInfo", method = {RequestMethod.POST, RequestMethod.GET})
public @ResponseBody
Object verifyLogin(HttpServletRequest request, HttpServletResponse response) {
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Map<String,Object> rs = new HashMap<>(); Map<String,Object> rs = new HashMap<>();
String userNameWithIdAttached = userDetails.getUsername(); String userNameWithIdAttached = userDetails.getUsername();
rs.put(Constants.Session_UserName, userNameWithIdAttached.split(":")[0]); rs.put(Constants.Session_UserName, userNameWithIdAttached.split(":")[0]);
...@@ -61,11 +87,131 @@ public class LoginManagement { ...@@ -61,11 +87,131 @@ public class LoginManagement {
return session.getAttribute(Constants.Session_Lang).toString(); return session.getAttribute(Constants.Session_Lang).toString();
} }
@RequestMapping(value = "/token", method = {RequestMethod.POST, RequestMethod.GET}) @GetMapping("/oauth/login")
public String token(HttpServletRequest request) { public void login(HttpServletResponse response) throws IOException {
request.getParameterNames(); String successAuthorizeUri = getAuthorizeFullUri();
return "ok"; log.info("adfsLoginUri is {}", successAuthorizeUri);
response.sendRedirect(successAuthorizeUri);
// return successAuthorizeUri;
}
private String getAuthorizeFullUri() {
String authorizeUri = oauth2Properties.getUserAuthorizationUri();
String clientId = oauth2Properties.getClientId();
String redirectUri = oauth2Properties.getPostLoginRedirectUri();
String response_type = Constants.OAUTH_AUTHORIZE_RESPONSE_TYPE;
String response_mode = 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, clientId, redirectUri, response_type, state, response_mode);
return authorizeFullUri;
}
@GetMapping("/oauth/logout")
public String logout(HttpServletRequest request){
String loginOutUri = oauth2Properties.getAuthorizationLoginOutUri();
HttpSession session = request.getSession();
String id_token = "";
if (session != null) {
id_token = (String)session.getAttribute(Constants.JWT_ID_TOKEN);
id_token = id_token == null ? "" : id_token;
}
String postLogoutRedirectUri = oauth2Properties.getPostLogoutRedirectUri();
String adfsLoginOutUri = String.format("%s?client_id=%s&id_token_hint=%s&post_logout_redirect_uri=%s",
loginOutUri, oauth2Properties.getClientId(), id_token, postLogoutRedirectUri);
log.info("adfsLoginOutUri url is {}", adfsLoginOutUri);
return adfsLoginOutUri;
}
@RequestMapping("/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("adfs回调携带参数----- code {} , state {} ", code, state);
Map<String, String> userDetailByTokenInfo = getUserDetailByTokenInfo(code, state, request);
List<Map<String, Object>> matchUser = authService.getUserByName(userDetailByTokenInfo.get("unique_name"));
if (null == matchUser || matchUser.isEmpty()) {
Map<String, Object> toAdd = new HashMap<>();
toAdd.put("dname", userDetailByTokenInfo.get("given_name"));
toAdd.put("name", userDetailByTokenInfo.get("unique_name"));
toAdd.put("password", "37fa265330ad83eaa879efb1e2db6380896cf639");
authService.addUser(toAdd);
}
UserDetails userDetails = customizedUserDetailService.loadUserByUsername(userDetailByTokenInfo.get("unique_name"));
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("code", code);
map.add("client_id", oauth2Properties.getClientId());
map.add("client_secret", oauth2Properties.getClientSecret());
map.add("redirect_uri", oauth2Properties.getPostLoginRedirectUri());
map.add("grant_type", Constants.OAUTH_AUTHORIZE_GRANT_TYPE);
log.info("adfs 获取token的url is {}, 参数为 {}", oauth2Properties.getAccessTokenUri(), map);
Map<String, String> resp = restTemplate.postForObject(oauth2Properties.getAccessTokenUri(), map, Map.class);
Object access_token = resp.get("access_token");
Object id_token = resp.get("id_token");
Object expires_in = resp.get("expires_in");
Object token_type = resp.get("token_tpye");
HttpSession session = request.getSession();
session.setAttribute(Constants.JWT_ACCESS_TOKEN, access_token);
session.setAttribute(Constants.JWT_ID_TOKEN, id_token);
session.setAttribute(Constants.JWT_EXPIRES_IN, expires_in);
session.setAttribute(Constants.JWT_TOKEN_TYPE, token_type);
log.info("从 adfs中获取到的 access_token is {}", access_token);
log.info("从 adfs中获取到的 id_token is {}", id_token);
return exactUserInfoFromToken((String)id_token);
}
throw new RuntimeException("adfs获取token的参数code或者state为空!");
} catch (Exception e) {
throw new RuntimeException("adfs 获取token 错误!", e);
}
}
private Map<String, String> exactUserInfoFromToken(String id_token) {
// id_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5qdGVqb1JUSm1BRm5HLVUxWlNUTFVnYWsyMCIsImtpZCI6Ik5qdGVqb1JUSm1BRm5HLVUxWlNUTFVnYWsyMCJ9.eyJhdWQiOiJjZmU1ZWRkMi1jNWNiLTQyMmItODZlMy1hZGY1MGQ0MmQ5ZTYiLCJpc3MiOiJodHRwczovL2FkZnNmb3Jtcy5taW5kcmF5LmNvbS9hZGZzIiwiaWF0IjoxNjY1OTgxMjkwLCJleHAiOjE2NjU5ODQ4OTAsImF1dGhfdGltZSI6MTY2NTk4MTI3NSwibWZhX2F1dGhfdGltZSI6MTY2NTk4MTI5MCwic3ViIjoiTFBFU2lWT3I0WXF0ZWJYRTRUaTBwdmZGYnFhN1BJdUxPbGtLK21YaGdGRT0iLCJ1cG4iOiJnYW96aGVuaHVhXzFAbWluZHJheS5jb20iLCJ1bmlxdWVfbmFtZSI6Ik1SQ05cXDgwNjAwNzQ1IiwicHdkX2V4cCI6IjM2Mzc2NzIiLCJzaWQiOiJTLTEtNS0yMS0zNDQxMjc5ODE2LTQwODU2Mzc1OTAtMzY1MzMxODM3MC0xOTEzODIiLCJnaXZlbl9uYW1lIjoi6auY5oyv5Y2OIiwiYXBwdHlwZSI6IlB1YmxpYyIsImFwcGlkIjoiY2ZlNWVkZDItYzVjYi00MjJiLTg2ZTMtYWRmNTBkNDJkOWU2IiwiYXV0aG1ldGhvZCI6InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0IiwidmVyIjoiMS4wIn0.YWls3k3_tDlSWyBNB1FQfm1zt2LNgjDOqltaENumApnxTXn_p3Iv8Hf4TXDp24wBkis9g8wimrt8NfoGpk4lVerocx1cwe24UZiXul8ufZXUPksPrWlWFMD-a2B4oI1DQzURssHyPFuCK097M9YLRGWuLMT9h3hHDDfmdVA2UeORHlRx2PJDYlKGbXG5HTdsIj-pFK_tgKziQ-hvNf-8fgiD-2KNmsUJ4g_5wUz5XP-fciAvKCjR-H07qgwQz8PFTD8282Yq9OVjsy3BqH4JtResySLIn7AbI9ng0ROFneIxUPBGyvjkNTm5GdIurV67w7B9d90jKjQq-JUPnWedlA";
try {
DecodedJWT decodedJWT = JWT.decode(id_token);
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 userInfoFromIdToken;
} catch (Exception e) {
throw new RuntimeException("解析token出错!", e);
}
}
} }
package com.keymobile.login.conf;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.auth.BasicAuthRequestInterceptor;
import feign.codec.Decoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
public class FeignClientConfig {
@Value("${feign.authUser}")
private String authUser;
@Value("${feign.authPwd}")
private String authPwd;
@Bean
public BasicAuthRequestInterceptor getBasicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor(authUser, authPwd);
}
@Bean
public Decoder feignDecoder() {
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
public ObjectMapper customObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
return objectMapper;
}
}
\ No newline at end of file
package com.keymobile.login.conf;
import com.keymobile.login.api.Constants;
import com.keymobile.login.oauth2.Oauth2Properties;
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.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Component
public class LogoutProcessHandler implements LogoutHandler {
private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
@Autowired
private Oauth2Properties oauth2Properties;
private RestTemplate restTemplate;
@PostConstruct
public void init() {
restTemplate = new RestTemplateBuilder().basicAuthorization(oauth2Properties.getClientId(), oauth2Properties.getClientSecret()).build();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
MediaType.TEXT_HTML,
MediaType.TEXT_PLAIN));
restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Map<String, Object> params = new HashMap<>();
HttpSession session = request.getSession();
if (session != null) {
String id_token = (String)session.getAttribute(Constants.JWT_ID_TOKEN);
if (null != id_token)
params.put("id_token_hint", id_token);
}
String adfsLoginOutUri = oauth2Properties.getAuthorizationLoginOutUri();
params.put("client_id", oauth2Properties.getClientId());
log.info("loginOutADFS url is {} ", adfsLoginOutUri);
log.info("loginOutADFS params is {} ", params);
try {
restTemplate.getForEntity(adfsLoginOutUri, String.class, params);
} catch (Exception e) {
log.error("loginOut error.", e);
}
}
}
package com.keymobile.login.conf; package com.keymobile.login.conf;
import com.keymobile.login.oauth2.Oauth2Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
...@@ -13,12 +15,16 @@ import java.io.IOException; ...@@ -13,12 +15,16 @@ import java.io.IOException;
@Component @Component
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler { public class RESTLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private Oauth2Properties oauth2Properties;
@Override @Override
public void onLogoutSuccess(HttpServletRequest request, public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) HttpServletResponse response, Authentication authentication)
throws IOException, ServletException { throws IOException, ServletException {
response.setStatus(HttpStatus.OK.value()); response.sendRedirect(oauth2Properties.getAuthorizeFullUri());
response.getWriter().flush(); // response.setStatus(HttpStatus.OK.value());
// response.getWriter().flush();
} }
} }
package com.keymobile.login.conf; package com.keymobile.login.conf;
import com.keymobile.auth.common.security.CustomizedUserDetailService; import com.keymobile.auth.common.security.CustomizedUserDetailService;
import com.keymobile.login.api.Constants;
import com.keymobile.login.oauth2.Oauth2Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.web.client.RestTemplate;
// import javax.servlet.http.HttpServletRequest;
//@Configuration import javax.servlet.http.HttpServletResponse;
//@ComponentScan("com.keymobile.auth.common.security") import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ComponentScan("com.keymobile.auth.common.security")
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
// @Autowired private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
@Autowired
private CustomizedUserDetailService customUserDetailService; private CustomizedUserDetailService customUserDetailService;
// @Autowired @Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint; private RESTAuthenticationEntryPoint authenticationEntryPoint;
// @Autowired @Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler; private RESTAuthenticationFailureHandler authenticationFailureHandler;
// @Autowired @Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler; private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
// @Autowired @Autowired
private RESTLogoutSuccessHandler logoutSuccessHandler; private RESTLogoutSuccessHandler logoutSuccessHandler;
@Autowired
private LogoutProcessHandler logoutProcessHandler;
private RestTemplate restTemplate = new RestTemplate();
// @Autowired @Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception { public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService).passwordEncoder(NoOpPasswordEncoder.getInstance()); auth.userDetailsService(customUserDetailService).passwordEncoder(NoOpPasswordEncoder.getInstance());
} }
// @Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll(); // http.authorizeRequests().anyRequest().authenticated();
http
.authorizeRequests()
.antMatchers("/login", "/error", "/signin", "/signout", "/oauth/**").permitAll()
.anyRequest().authenticated();
http.csrf().disable(); http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler); http.formLogin().successHandler(authenticationSuccessHandler);
...@@ -44,4 +68,5 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -44,4 +68,5 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.logout().logoutSuccessHandler(logoutSuccessHandler); http.logout().logoutSuccessHandler(logoutSuccessHandler);
} }
} }
package com.keymobile.login.oauth2; package com.keymobile.login.oauth2;
import com.keymobile.auth.common.security.CustomizedUserDetailService; import com.keymobile.auth.common.security.CustomizedUserDetailService;
import org.springframework.boot.web.client.RestTemplateBuilder; import com.keymobile.login.api.Constants;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.util.Collection;
import java.util.Date;
import java.util.Map; import java.util.Map;
public class AccessTokenInterceptor implements HandlerInterceptor { public class AccessTokenInterceptor implements HandlerInterceptor {
...@@ -32,52 +25,72 @@ public class AccessTokenInterceptor implements HandlerInterceptor { ...@@ -32,52 +25,72 @@ public class AccessTokenInterceptor implements HandlerInterceptor {
private CustomizedUserDetailService userDetailService; private CustomizedUserDetailService userDetailService;
public AccessTokenInterceptor(RestTemplate restTemplate, CustomizedUserDetailService customizedUserDetailService) { private Oauth2Properties oauth2Properties;
public AccessTokenInterceptor(Oauth2Properties oauth2Properties, RestTemplate restTemplate, CustomizedUserDetailService customizedUserDetailService) {
this.userDetailService = customizedUserDetailService; this.userDetailService = customizedUserDetailService;
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
this.oauth2Properties = oauth2Properties;
} }
// 在请求处理之前,只有返回true才会执行请求 // 在请求处理之前,只有返回true才会执行请求
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 得到session // 得到session
System.out.println( Thread.currentThread().toString() + "AccessToken request url-------------------" + request.getRequestURI());
UserDetails userDetails = null; UserDetails userDetails = null;
try { try {
userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) { } catch (Exception e) {
userDetails = getUserDetailByTokenInfo(request); userDetails = getUserDetailByTokenInfo(request);
if (null != userDetails) { if (null != userDetails) {
response.sendRedirect("http://localhost:8089/center-home/menu/index"); response.sendRedirect(oauth2Properties.getAuthorizationSuccessRedirectUri());
return true; return true;
} }
response.sendRedirect("http://localhost:1111/oauth/authorize?client_id=javaboy&redirect_uri=http://localhost:8089/api/auth/token&response_type=code&state=3UJF8q"); String authorizeFullUri = getAuthorizeFullUri();
response.sendRedirect(authorizeFullUri);
return false; return false;
} }
return true; return true;
} }
private String getAuthorizeFullUri() {
String authorizeUri = oauth2Properties.getUserAuthorizationUri();
String clientId = oauth2Properties.getClientId();
String redirectUri = oauth2Properties.getPostLoginRedirectUri();
String response_type = Constants.OAUTH_AUTHORIZE_RESPONSE_TYPE;
String response_mode = 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, clientId, redirectUri, response_type, state, response_mode);
return authorizeFullUri;
}
private UserDetails getUserDetailByTokenInfo(HttpServletRequest request) { private UserDetails getUserDetailByTokenInfo(HttpServletRequest request) {
String code = request.getParameter("code"); try {
String state = request.getParameter("state"); String code = request.getParameter(Constants.OAUTH_AUTHORIZE_CODE_PARAM);
String state = request.getParameter(Constants.OAUTH_AUTHORIZE_STATE_PARAM);
System.out.println("accessToken request 获取到code" + code + ",获取到state " + state);
UserDetails userDetails = null; UserDetails userDetails = null;
if (!StringUtils.isEmpty(code) && !StringUtils.isEmpty(state)) { if (!StringUtils.isEmpty(code) && !StringUtils.isEmpty(state)) {
if (code != null) { if (code != null) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("code", code); map.add("code", code);
map.add("client_id", "javaboy"); map.add("client_id", oauth2Properties.getClientId());
map.add("client_secret", "123"); map.add("client_secret", oauth2Properties.getClientSecret());
map.add("redirect_uri", "http://localhost:8089/api/auth/token"); map.add("redirect_uri", oauth2Properties.getPostLoginRedirectUri());
map.add("grant_type", "authorization_code"); map.add("grant_type", Constants.OAUTH_AUTHORIZE_GRANT_TYPE);
restTemplate = new RestTemplateBuilder().basicAuthorization("javaboy", "123").build(); Map<String, String> resp = restTemplate.postForObject(oauth2Properties.getAccessTokenUri(), map, Map.class);
Map<String, String> resp = restTemplate.postForObject("http://localhost:1111/oauth/token", map, Map.class);
String access_token = resp.get("access_token"); Object access_token = resp.get("access_token");
String id_token = resp.get("id_token"); Object id_token = resp.get("id_token");
if (!StringUtils.isEmpty(id_token)) { Object expires_in = resp.get("expires_in");
Object token_type = resp.get("token_tpye");
}
System.out.println("获取到token......" + access_token); System.out.println("获取到token......" + access_token);
System.out.println("获取到id_token......" + id_token); System.out.println("获取到id_token......" + id_token);
String username = exactUserInfoFromToken(access_token);
String username = exactUserInfoFromToken((String)access_token);
userDetails = userDetailService.loadUserByUsername(username); userDetails = userDetailService.loadUserByUsername(username);
...@@ -87,18 +100,19 @@ public class AccessTokenInterceptor implements HandlerInterceptor { ...@@ -87,18 +100,19 @@ public class AccessTokenInterceptor implements HandlerInterceptor {
authentication.setDetails(new WebAuthenticationDetails(request)); authentication.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
HttpSession session = request.getSession(true); HttpSession session = request.getSession(true);
session.setAttribute("access_token", access_token); session.setAttribute(Constants.JWT_ACCESS_TOKEN, access_token);
session.setAttribute("id_token", id_token); session.setAttribute(Constants.JWT_ID_TOKEN, id_token);
session.setAttribute(Constants.JWT_EXPIRES_IN, expires_in);
session.setAttribute(Constants.JWT_TOKEN_TYPE, token_type);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
// HttpHeaders headers = new HttpHeaders();
// headers.add("Authorization", "Bearer " + access_token);
// HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
// ResponseEntity<String> entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, httpEntity, String.class);
// model.addAttribute("msg", entity.getBody());
// restTemplate.getForEntity("http://localhost:1111", Map.class).getBody();
} }
} }
return userDetails; return userDetails;
} catch (Exception e) {
e.printStackTrace();
}
return null;
} }
// 视图渲染后执行 // 视图渲染后执行
...@@ -113,6 +127,7 @@ public class AccessTokenInterceptor implements HandlerInterceptor { ...@@ -113,6 +127,7 @@ public class AccessTokenInterceptor implements HandlerInterceptor {
private String exactUserInfoFromToken(String access_token) { private String exactUserInfoFromToken(String access_token) {
//暂时写死 //暂时写死
return "root"; return "root";
} }
......
package com.keymobile.login.oauth2;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.keymobile.auth.common.security.CustomizedUserDetailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.FixedAuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.FixedPrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.stereotype.Component;
import org.apache.commons.codec.binary.Base64;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AdfsUserInfoTokenServices implements ResourceServerTokenServices {
protected final Logger logger = LoggerFactory.getLogger(getClass());
private final String userInfoEndpointUrl;
private CustomizedUserDetailService customizedUserDetailService;
private final String clientId;
private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
private PrincipalExtractor principalExtractor = new FixedPrincipalExtractor();
public AdfsUserInfoTokenServices(CustomizedUserDetailService customizedUserDetailService, String userInfoEndpointUrl, String clientId) {
this.userInfoEndpointUrl = userInfoEndpointUrl;
this.clientId = clientId;
this.customizedUserDetailService = customizedUserDetailService;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
if (map.containsKey("error")) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("userinfo returned error: " + map.get("error"));
}
throw new InvalidTokenException(accessToken);
}
return extractAuthentication(map);
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
//adfs目前传了两个字段过来
String userNo = map.get("sub") == null ? "unknow" : (String)map.get("sub");
String userName = map.get("given_name") == null ? "unknow" : (String)map.get("given_name");
UserDetails userDetails = customizedUserDetailService.loadUserByUsername("root");
// Object principal = getPrincipal(map);
// List<GrantedAuthority> authorities = this.authoritiesExtractor
// .extractAuthorities(map);
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
null, null, null, null);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
// UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
// userDetails.get, "N/A", authorities);
// ExpiringUsernameAuthenticationToken result = new ExpiringUsernameAuthenticationToken(expiration, principal, authenticationCredential, entitlements);
token.setDetails(userDetails);
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = httpServletRequest.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
return new OAuth2Authentication(request, token);
}
protected Object getPrincipal(Map<String, Object> map) {
Object principal = this.principalExtractor.extractPrincipal(map);
return (principal == null ? "unknown" : principal);
}
private Map<String, Object> getMap(String path, String accessToken) {
//暂时写死
Map<String, Object> rt = new HashMap<>();
rt.put("sub", "root");
rt.put("given_name", "root");
return rt;
//迈瑞的信息从token里面提取
// if (this.logger.isDebugEnabled()) {
// this.logger.debug("Getting user info from: " + path);
// }
// try {
// DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
// accessToken);
// token.setTokenType(this.tokenType);
//
// logger.debug("Token value: " + token.getValue());
//
// String jwtBase64 = token.getValue().split("\\.")[1];
//
// logger.debug("Token: Encoded JWT: " + jwtBase64);
// org.apache.commons.codec.binary.Base64 base64 = new Base64();
// logger.debug("Decode: " + base64.decode(jwtBase64.getBytes()));
// String jwtJson = new String(base64.decode(jwtBase64.getBytes()));
// ObjectMapper mapper = new ObjectMapper();
// return mapper.readValue(jwtJson, new TypeReference<Map<String, Object>>(){});
// }
// catch (Exception ex) {
// this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
// + ex.getMessage());
// return Collections.<String, Object>singletonMap("error",
// "Could not fetch user details");
// }
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
return null;
}
}
package com.keymobile.login.oauth2;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.filter.OAuth2AuthenticationFailureEvent;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetailsSource;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.Assert;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomOauth2ClientAuthenticationProcessFilter extends OAuth2ClientAuthenticationProcessingFilter {
public CustomOauth2ClientAuthenticationProcessFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(new NoopAuthenticationManager());
setAuthenticationDetailsSource(authenticationDetailsSource);
}
public OAuth2RestOperations restTemplate;
private ResourceServerTokenServices tokenServices;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
private ApplicationEventPublisher eventPublisher;
/**
* Reference to a CheckTokenServices that can validate an OAuth2AccessToken
*
* @param tokenServices
*/
public void setTokenServices(ResourceServerTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
/**
* A rest template to be used to obtain an access token.
*
* @param restTemplate a rest template
*/
public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
super.setApplicationEventPublisher(eventPublisher);
}
@Override
public void afterPropertiesSet() {
Assert.state(restTemplate != null, "Supply a rest-template");
super.afterPropertiesSet();
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
try {
OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
if (authenticationDetailsSource!=null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
}
catch (InvalidTokenException e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
private void publish(ApplicationEvent event) {
if (eventPublisher!=null) {
eventPublisher.publishEvent(event);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
// Nearly a no-op, but if there is a ClientTokenServices then the token will now be stored
restTemplate.getAccessToken();
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
if (failed instanceof AccessTokenRequiredException) {
// Need to force a redirect via the OAuth client filter, so rethrow here
throw failed;
}
else {
// If the exception is not a Spring Security exception this will result in a default error page
super.unsuccessfulAuthentication(request, response, failed);
}
}
private static class NoopAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
}
}
}
...@@ -2,29 +2,26 @@ package com.keymobile.login.oauth2; ...@@ -2,29 +2,26 @@ package com.keymobile.login.oauth2;
import com.keymobile.auth.common.security.CustomizedUserDetailService; import com.keymobile.auth.common.security.CustomizedUserDetailService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration //@Configuration
public class LoginConfig implements WebMvcConfigurer { public class LoginConfig implements WebMvcConfigurer {
/**
* 该方法用于注册拦截器
* 可注册多个拦截器,多个拦截器组成一个拦截器链
*/
@Autowired @Autowired
private CustomizedUserDetailService customizedUserDetailService; private CustomizedUserDetailService customizedUserDetailService;
@Autowired
private Oauth2Properties oauth2Properties;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns 添加路径 // registry.addInterceptor(new LoginInterceptor(oauth2Properties)).excludePathPatterns("/oauth/**");
// excludePathPatterns 排除路径 // registry.addInterceptor(new AccessTokenInterceptor(oauth2Properties,
registry.addInterceptor(new LoginInterceptor()); // new RestTemplateBuilder().basicAuthorization(oauth2Properties.getClientId(),
registry.addInterceptor(new AccessTokenInterceptor(new RestTemplate(), customizedUserDetailService)); // oauth2Properties.getClientSecret()).build(), customizedUserDetailService)).excludePathPatterns("/oauth/**");
// .addPathPatterns("/sys/*"); // 拦截sys路径下的url
// .excludePathPatterns("");
} }
} }
\ No newline at end of file
package com.keymobile.login.oauth2; package com.keymobile.login.oauth2;
import org.slf4j.LoggerFactory; import com.keymobile.login.api.Constants;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -9,24 +9,34 @@ import org.springframework.web.servlet.ModelAndView; ...@@ -9,24 +9,34 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor { public class LoginInterceptor implements HandlerInterceptor {
private Oauth2Properties oauth2Properties;
public LoginInterceptor(Oauth2Properties oauth2Properties) {
this.oauth2Properties = oauth2Properties;
}
// 在请求处理之前,只有返回true才会执行请求 // 在请求处理之前,只有返回true才会执行请求
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 得到session // 得到session
String code = request.getParameter("code"); System.out.println(Thread.currentThread().toString() + "login request url-------------------" + request.getRequestURI());
String state = request.getParameter("state"); String code = request.getParameter(Constants.OAUTH_AUTHORIZE_CODE_PARAM);
String state = request.getParameter(Constants.OAUTH_AUTHORIZE_STATE_PARAM);
System.out.println("login request 获取到code" + code + ",获取到state " + state);
if (!StringUtils.isEmpty(code) && !StringUtils.isEmpty(state)) { if (!StringUtils.isEmpty(code) && !StringUtils.isEmpty(state)) {
return true; return true;
} }
UserDetails userDetails = null;
try { try {
userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (null != userDetails) {
return true;
}
} catch (Exception e) { } catch (Exception e) {
response.sendRedirect("http://localhost:1111/oauth/authorize?client_id=javaboy&redirect_uri=http://localhost:8089/api/auth/token&response_type=code&state=3UJF8q"); String authorizeFullUri = getAuthorizeFullUri();
response.sendRedirect(authorizeFullUri);
return false; return false;
} }
return true; return true;
...@@ -39,4 +49,16 @@ public class LoginInterceptor implements HandlerInterceptor { ...@@ -39,4 +49,16 @@ public class LoginInterceptor implements HandlerInterceptor {
@Override @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
} }
private String getAuthorizeFullUri() {
String authorizeUri = oauth2Properties.getUserAuthorizationUri();
String clientId = oauth2Properties.getClientId();
String redirectUri = oauth2Properties.getPostLoginRedirectUri();
String response_type = Constants.OAUTH_AUTHORIZE_RESPONSE_TYPE;
String response_mode = 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, clientId, redirectUri, response_type, state, response_mode);
return authorizeFullUri;
}
} }
\ No newline at end of file
package com.keymobile.login.oauth2;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//@Component
public class MyOAuth2ClientAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
public MyOAuth2ClientAuthenticationProcessingFilter() {
super("http://localhost:8082/login");
}
@Override
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
new SimpleUrlAuthenticationSuccessHandler() {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException, ServletException {
this.setDefaultTargetUrl("/home");
super.onAuthenticationSuccess(request, response, authentication);
}
};
}
}
package com.keymobile.login.oauth2;
import com.keymobile.auth.common.security.CustomizedUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
//@Configuration
public class Oauth2ClientConfig {
// @Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext context, OAuth2ProtectedResourceDetails details) {
OAuth2RestTemplate template = new OAuth2RestTemplate(details, context);
AuthorizationCodeAccessTokenProvider authCodeProvider = new AuthorizationCodeAccessTokenProvider();
authCodeProvider.setStateMandatory(false);
AccessTokenProviderChain provider = new AccessTokenProviderChain(
Arrays.asList(authCodeProvider));
template.setAccessTokenProvider(provider);
return template;
}
/**
* 注册处理redirect uri的filter
* @param oauth2RestTemplate
* @param tokenService
* @return
*/
// @Bean
public OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter(
OAuth2RestTemplate oauth2RestTemplate,
AdfsUserInfoTokenServices tokenService) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter("http://localhost:8082/login");
filter.setRestTemplate(oauth2RestTemplate);
filter.setTokenServices(tokenService);
//设置回调成功的页面
filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler() {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException, IOException, ServletException {
this.setDefaultTargetUrl("/home");
super.onAuthenticationSuccess(request, response, authentication);
}
});
return filter;
}
/**
* 注册check token服务
* @param details
* @return
*/
// @Bean
// public RemoteTokenServices tokenService(OAuth2ProtectedResourceDetails details) {
// RemoteTokenServices tokenService = new RemoteTokenServices();
// tokenService.setCheckTokenEndpointUrl("https://adfsforms.mindray.com/adfs/oauth2/token");
// tokenService.setClientId(details.getClientId());
// tokenService.setClientSecret(details.getClientSecret());
// return tokenService;
// }
}
package com.keymobile.login.oauth2;
import com.keymobile.login.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 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 getAuthorizeFullUri() {
String authorizeUri = getUserAuthorizationUri();
String clientId = getClientId();
String redirectUri = getPostLoginRedirectUri();
String response_type = Constants.OAUTH_AUTHORIZE_RESPONSE_TYPE;
String response_mode = 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, clientId, redirectUri, response_type, state, response_mode);
return authorizeFullUri;
}
}
package com.keymobile.login.saml;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@ConfigurationProperties("demo.saml.context.provider")
@Configuration
public class ContextProperties {
public ContextProperties() {};
private String schema;
private String serverName;
private String contextPath;
private int serverPort;
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public int getServerPort() {
return serverPort;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
}
package com.keymobile.login.saml;
import org.opensaml.common.SAMLException;
import org.opensaml.common.SAMLRuntimeException;
import org.opensaml.xml.encryption.DecryptionException;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.validation.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
import org.springframework.security.saml.SAMLAuthenticationProvider;
import org.springframework.security.saml.SAMLAuthenticationToken;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.context.SAMLMessageContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider {
private boolean excludeCredential = false;
private static final Logger log = LoggerFactory.getLogger(SAMLAuthenticationProvider.class);
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!this.supports(authentication.getClass())) {
throw new IllegalArgumentException("Only SAMLAuthenticationToken is supported, " + authentication.getClass() + " was attempted");
} else {
SAMLAuthenticationToken token = (SAMLAuthenticationToken)authentication;
SAMLMessageContext context = token.getCredentials();
if (context == null) {
throw new AuthenticationServiceException("SAML message context is not available in the authentication token");
} else {
SAMLCredential credential;
try {
if ("urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser".equals(context.getCommunicationProfileId())) {
credential = this.consumer.processAuthenticationResponse(context);
} else {
if (!"urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser".equals(context.getCommunicationProfileId())) {
throw new SAMLException("Unsupported profile encountered in the context " + context.getCommunicationProfileId());
}
credential = this.hokConsumer.processAuthenticationResponse(context);
}
} catch (SAMLRuntimeException var11) {
log.debug("Error validating SAML message", var11);
this.samlLogger.log("AuthNResponse", "FAILURE", context, var11);
throw new AuthenticationServiceException("Error validating SAML message", var11);
} catch (SAMLException var12) {
log.debug("Error validating SAML message", var12);
this.samlLogger.log("AuthNResponse", "FAILURE", context, var12);
throw new AuthenticationServiceException("Error validating SAML message", var12);
} catch (ValidationException var13) {
log.debug("Error validating signature", var13);
this.samlLogger.log("AuthNResponse", "FAILURE", context, var13);
throw new AuthenticationServiceException("Error validating SAML message signature", var13);
} catch (SecurityException var14) {
log.debug("Error validating signature", var14);
this.samlLogger.log("AuthNResponse", "FAILURE", context, var14);
throw new AuthenticationServiceException("Error validating SAML message signature", var14);
} catch (DecryptionException var15) {
log.debug("Error decrypting SAML message", var15);
this.samlLogger.log("AuthNResponse", "FAILURE", context, var15);
throw new AuthenticationServiceException("Error decrypting SAML message", var15);
}
Object userDetails = this.getUserDetails(credential);
Object principal = this.getPrincipal(credential, userDetails);
Collection<? extends GrantedAuthority> entitlements = this.getEntitlements(credential, userDetails);
Date expiration = this.getExpirationDate(credential);
SAMLCredential authenticationCredential = this.excludeCredential ? null : credential;
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authenticationCredential, entitlements);
// ExpiringUsernameAuthenticationToken result = new ExpiringUsernameAuthenticationToken(expiration, principal, authenticationCredential, entitlements);
result.setDetails(userDetails);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
this.samlLogger.log("AuthNResponse", "SUCCESS", context, result, (Exception)null);
return result;
}
}
}
}
package com.keymobile.login.saml;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
@Configuration
public class FilterCleanupConfig {
@Bean
public static BeanDefinitionRegistryPostProcessor removeUnwantedAutomaticFilterRegistration() {
return new BeanDefinitionRegistryPostProcessor() {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
final DefaultListableBeanFactory bf = (DefaultListableBeanFactory) beanFactory;
final List<String> filtersToDisable = Arrays.asList(
"samlEntryPoint", "samlFilter", "metadataDisplayFilter",
"samlWebSSOHoKProcessingFilter", "samlWebSSOProcessingFilter",
"samlLogoutProcessingFilter", "samlLogoutFilter", "metadataGeneratorFilter"
// "samlEntryPoint", "samlFilter", "samlIDPDiscovery", "metadataDisplayFilter",
// "samlWebSSOHoKProcessingFilter", "samlWebSSOProcessingFilter",
// "samlLogoutProcessingFilter", "samlLogoutFilter", "metadataGeneratorFilter"
);
Arrays.stream(beanFactory.getBeanNamesForType(javax.servlet.Filter.class))
.filter(filtersToDisable::contains)
.forEach(name -> {
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(FilterRegistrationBean.class)
.setScope(BeanDefinition.SCOPE_SINGLETON)
.addConstructorArgReference(name)
.addConstructorArgValue(new ServletRegistrationBean[]{})
.addPropertyValue("enabled", false)
.getBeanDefinition();
bf.registerBeanDefinition(name + "FilterRegistrationBean", definition);
});
}
};
}
}
package com.keymobile.login.saml;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.saml.key.JKSKeyManager;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
public class KeystoreFactory {
private static final String FLAG_START = "-----BEGIN RSA PRIVATE KEY-----";
private static final String FLAG_END = "-----END RSA PRIVATE KEY-----";
private static final String DEFAULT_KEY_ALIAS = "defaultKeyAlias";
private static final char[] DEFAULT_KEY_STORE_PASS = "defaultKeyStorePass".toCharArray();
private static final BouncyCastleProvider BC = new BouncyCastleProvider();
/**
* 获取 JKSKeyManager。
*
* @param publicKeyCertLocation 公钥证书所在位置
* @param privateKeyCertLocation 私钥证书所在位置
* @return JKSKeyManager
*/
public JKSKeyManager getJKSKeyManager(String publicKeyCertLocation, String privateKeyCertLocation) throws Exception {
KeyStore keystore = createEmptyKeystore();
final Certificate cert = loadCert(publicKeyCertLocation);
PrivateKey privateKey = loadPrivateKey(privateKeyCertLocation);
addKeyToKeystore(keystore, cert, privateKey, DEFAULT_KEY_ALIAS, DEFAULT_KEY_STORE_PASS);
return createJKSKeyManager(keystore,
Collections.singletonMap(DEFAULT_KEY_ALIAS, new String(DEFAULT_KEY_STORE_PASS)),
DEFAULT_KEY_ALIAS);
}
/**
* 获取 JKSKeyManager
*
* @param keyStoreLocation jks 所在位置,该 jks 必须只包 1 把私钥
* @param keyStorePassword jks 的密码
* @param keyPassword 私钥的密码
* @return JKSKeyManager
*/
public JKSKeyManager getJKSKeyManager(String keyStoreLocation, char[] keyStorePassword, char[] keyPassword) throws Exception {
final File keyStoreFile = ResourceUtils.getFile(keyStoreLocation);
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keyStoreFile), keyStorePassword);
final Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
final String alias = aliases.nextElement();
if (ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) {
final Key key = ks.getKey(alias, keyPassword);
if (key == null) {
throw new IllegalStateException("Can't get private key in keystore with the given key password.");
}
return createJKSKeyManager(ks, Collections.singletonMap(alias, new String(keyPassword)), alias);
}
}
throw new IllegalStateException("Can't find any private key in keystore " + keyStoreLocation);
}
public void addKeyToKeystore(KeyStore keyStore, Certificate cert, PrivateKey privateKey, String alias, char[] password) throws Exception {
KeyStore.PasswordProtection pass = new KeyStore.PasswordProtection(password);
Certificate[] certificateChain = {cert};
keyStore.setEntry(alias, new KeyStore.PrivateKeyEntry(privateKey, certificateChain), pass);
}
public KeyStore createEmptyKeystore() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, "".toCharArray());
return keyStore;
}
public JKSKeyManager createJKSKeyManager(KeyStore keyStore, Map<String, String> passwords, String defaultKey) {
return new JKSKeyManager(keyStore, passwords, defaultKey);
}
public Certificate loadCert(String certLocation) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X509");
ClassPathResource classPathResource = new ClassPathResource(certLocation);
InputStream pathResourceInputStream = classPathResource.getInputStream();
// final File publicKeyCertFile = ResourceUtils.getFile(certLocation);
return cf.generateCertificate(pathResourceInputStream);
}
public PrivateKey loadPrivateKey(String privateKeyLocation) throws Exception {
// final File privateKeyFile = ResourceUtils.getFile(privateKeyLocation);
ClassPathResource classPathResource = new ClassPathResource(privateKeyLocation);
InputStream pathResourceInputStream = classPathResource.getInputStream();
byte[] keyBytes = StreamUtils.copyToByteArray(pathResourceInputStream);
if (privateKeyLocation.endsWith(".key")) {
final String pvKey = new String(keyBytes);
final String base64 = pvKey.replaceAll("\r", "").replaceAll("\n", "")
.replaceAll(FLAG_START, "").replaceAll(FLAG_END, "");
keyBytes = Base64.getDecoder().decode(base64);
}
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA", BC);
return keyFactory.generatePrivate(privateKeySpec);
}
}
\ No newline at end of file
package com.keymobile.login.saml;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/protected").setViewName("protected");
}
}
package com.keymobile.login.saml;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.app.VelocityEngine;
import org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider;
import org.opensaml.util.resource.FilesystemResource;
import org.opensaml.util.resource.Resource;
import org.opensaml.xml.parse.StaticBasicParserPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.saml.*;
import org.springframework.security.saml.context.SAMLContextProviderImpl;
import org.springframework.security.saml.context.SAMLContextProviderLB;
import org.springframework.security.saml.key.EmptyKeyManager;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.metadata.*;
import org.springframework.security.saml.processor.*;
import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer;
import org.springframework.security.saml.util.VelocityFactory;
import org.springframework.security.saml.websso.ArtifactResolutionProfileImpl;
import org.springframework.security.saml.websso.WebSSOProfileOptions;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Timer;
@Configuration
@EnableConfigurationProperties(SamlProperties.class)
public class SAMLConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(SAMLConfig.class);
private final SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
private final SamlProperties samlProperties;
@Value("${server.context-path:}")
private String serverContextPath;
@Autowired
public SAMLConfig(SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl, SamlProperties samlProperties) {
this.samlUserDetailsServiceImpl = samlUserDetailsServiceImpl;
this.samlProperties = samlProperties;
}
// @Bean
// public SAMLAuthenticationProvider samlAuthenticationProvider() {
// SAMLAuthenticationProvider provider = new SAMLAuthenticationProvider();
// // 在验证用户 SAML 断言时自动调用 SAMLUserDetailsServiceImpl
// provider.setUserDetails(samlUserDetailsServiceImpl);
// // 默认情况下,返回的Authentication对象中的主体是身份验证的断言中包含的NameID。NameID不可序列化。将此值设置为true将强制NameID值为字符串。
// provider.setForcePrincipalAsString(false);
// return provider;
// }
//换成自定义的
@Bean
public CustomSAMLAuthenticationProvider samlAuthenticationProvider() {
CustomSAMLAuthenticationProvider provider = new CustomSAMLAuthenticationProvider();
// 在验证用户 SAML 断言时自动调用 SAMLUserDetailsServiceImpl
provider.setUserDetails(samlUserDetailsServiceImpl);
// 默认情况下,返回的Authentication对象中的主体是身份验证的断言中包含的NameID。NameID不可序列化。将此值设置为true将强制NameID值为字符串。
provider.setForcePrincipalAsString(false);
return provider;
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Collections.singletonList(samlAuthenticationProvider()));
}
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean
public SAMLProcessorImpl processor() {
HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient);
HTTPSOAP11Binding soapBinding = new HTTPSOAP11Binding(parserPool());
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding));
VelocityEngine velocityEngine = VelocityFactory.getEngine();
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(new HTTPRedirectDeflateBinding(parserPool()));
bindings.add(new HTTPPostBinding(parserPool(), velocityEngine));
bindings.add(new HTTPArtifactBinding(parserPool(), velocityEngine, artifactResolutionProfile));
bindings.add(new HTTPSOAP11Binding(parserPool()));
bindings.add(new HTTPPAOS11Binding(parserPool()));
return new SAMLProcessorImpl(bindings);
}
@Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler handler = new SimpleUrlLogoutSuccessHandler();
handler.setDefaultTargetUrl("/");
return handler;
}
@Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler handler = new SecurityContextLogoutHandler();
//handler.setInvalidateHttpSession(true);
handler.setClearAuthentication(true);
return handler;
}
@Bean
public SAMLLogoutFilter samlLogoutFilter() {
SAMLLogoutFilter filter = new SAMLLogoutFilter(successLogoutHandler(), new LogoutHandler[]{logoutHandler()}, new LogoutHandler[]{logoutHandler()});
// 本地登出
filter.setFilterProcessesUrl("/saml/logout");
return filter;
}
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
SAMLLogoutProcessingFilter filter = new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
// SLO 登出,即在 IDP 登出
filter.setFilterProcessesUrl("/saml/SingleLogout");
return filter;
}
// 配置 SP 元数据的生成
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter(MetadataGenerator metadataGenerator) {
return new MetadataGeneratorFilter(metadataGenerator);
}
@Bean
public MetadataDisplayFilter metadataDisplayFilter() throws Exception {
MetadataDisplayFilter filter = new MetadataDisplayFilter();
// 设置 IDP 元数据地址
filter.setFilterProcessesUrl("/saml/metadata");
return filter;
}
@Bean
public ExtendedMetadataDelegate idpMetadataLoader() {
if (StringUtils.isBlank(samlProperties.getIdpXml()) || !samlProperties.getIdpXml().endsWith(".xml")) {
throw new IllegalArgumentException("demo.saml.idp-xml must not be null or empty and must be a xml file.");
}
try {
ClassPathResource classPathResource = new ClassPathResource(samlProperties.getIdpXml());
InputStream pathResourceInputStream = classPathResource.getInputStream();
File templateFile = new File(samlProperties.getIdpXml());
FileUtils.copyToFile(pathResourceInputStream, templateFile);
Resource idpResource = new FilesystemResource(templateFile.getPath());
Timer refreshTimer = new Timer(true);
ResourceBackedMetadataProvider delegate;
delegate = new ResourceBackedMetadataProvider(refreshTimer, idpResource);
delegate.setParserPool(parserPool());
ExtendedMetadata extendedMetadata = extendedMetadata().clone();
ExtendedMetadataDelegate provider = new ExtendedMetadataDelegate(delegate, extendedMetadata);
provider.setMetadataTrustCheck(false);
provider.setMetadataRequireSignature(false);
// String idpName = file.getName().replaceAll(".xml", "");
// extendedMetadata.setAlias(idpName);
// 配置 IDP 元数据的 provider
// LOGGER.info("Loaded Idp Metadata bean {}: {}", idpName, file.getPath());
return provider;
} catch (Exception e) {
throw new IllegalStateException("Unable to initialize IDP Metadata", e);
}
}
// 额外的元数据
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata metadata = new ExtendedMetadata();
//set flag to true to present user with IDP Selection screen
// metadata.setIdpDiscoveryEnabled(true);
metadata.setRequireLogoutRequestSigned(true);
metadata.setRequireLogoutResponseSigned(true);
metadata.setSignMetadata(false);
return metadata;
}
// SP元数据生成器
@Bean
public MetadataGenerator metadataGenerator(KeyManager keyManager) {
MetadataGenerator generator = new MetadataGenerator();
// generator.setEntityId("localhost-demo");
// SP 标识
generator.setEntityId(samlProperties.getEntityId());
generator.setExtendedMetadata(extendedMetadata());
// 如果为true,则生成的元数据将包含扩展名,指示其能够使用来自IDP发现服务的响应。
generator.setIncludeDiscoveryExtension(false);
generator.setKeyManager(keyManager);
generator.setRequestSigned(samlProperties.getSignMetadata());
generator.setWantAssertionSigned(samlProperties.getWantAssertionSigned());
if (samlProperties.getUseLB()) {
String schema = samlProperties.getContext().getSchema();
String ip = samlProperties.getContext().getServerName();
int port = samlProperties.getContext().getServerPort();
String contextPath = samlProperties.getContext().getContextPath();
String entityBaseURL = String.format("%s://%s:%s%s", schema, ip, port, contextPath);
generator.setEntityBaseURL(entityBaseURL);
}
return generator;
}
@Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter filter = new SAMLProcessingFilter();
// filter.setContextProvider(contextProvider());
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successRedirectHandler());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
// 在 IDP 登录后跳转到 SP 的地址,也就是所谓的断言消费者
String ssoProcessUrl = "/saml/SSO";
if (StringUtils.isNotBlank(serverContextPath)) {
ssoProcessUrl = serverContextPath + ssoProcessUrl;
}
filter.setFilterProcessesUrl(ssoProcessUrl);
return filter;
}
@Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter filter = new SAMLWebSSOHoKProcessingFilter();
filter.setAuthenticationSuccessHandler(successRedirectHandler());
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
@Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
handler.setAlwaysUseDefaultTargetUrl(true);
handler.setDefaultTargetUrl(samlProperties.getSuccessTargetUrl());
return handler;
}
@Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler();
handler.setUseForward(false);
//handler.setDefaultFailureUrl("/error");
return handler;
}
// 用于配置多个 IDP
// @Bean
// public SAMLDiscovery samlIDPDiscovery() {
// SAMLDiscovery filter = new SAMLDiscovery();
// filter.setFilterProcessesUrl("/saml/discovery");
// filter.setIdpSelectionPath("/idpselection");
// return filter;
// }
@Bean
public SAMLEntryPoint samlEntryPoint() {
// WebSSOProfileOptions 用于自定义AuthnRequest的构造和用于发送它的绑定
WebSSOProfileOptions options = new WebSSOProfileOptions();
// 默认值:true。当为true时,请求将包括作用域元素。
options.setIncludeScoping(false);
SAMLEntryPoint entryPoint = new SAMLEntryPoint();
entryPoint.setDefaultProfileOptions(options);
entryPoint.setFilterProcessesUrl("/saml/login");
return entryPoint;
}
@Bean
public KeystoreFactory keystoreFactory() {
return new KeystoreFactory();
}
@Bean
public KeyManager keyManager(KeystoreFactory keystoreFactory) throws Exception {
LOGGER.debug("Start to initialize KeyManager for SAML.");
LOGGER.debug("Check demo.saml.public-key-cert and demo.saml.private-key-cert.");
if (samlProperties.useCerts()) {
LOGGER.debug("find demo.saml.public-key-cert and demo.saml.private-key-cert.");
LOGGER.debug("Use demo.saml.public-key-cert and demo.saml.private-key-cert to initialize KeyManager.");
return keystoreFactory.getJKSKeyManager(samlProperties.getPublicKeyCert(), samlProperties.getPrivateKeyCert());
}
LOGGER.debug("Can't find demo.saml.public-key-cert and demo.saml.private-key-cert.");
LOGGER.debug("Check demo.saml.key-store and demo.saml.key-alias.");
if (samlProperties.useKeyStore()) {
LOGGER.debug("find demo.saml.key-store and demo.saml.key-alias.");
try {
return keystoreFactory.getJKSKeyManager(samlProperties.getKeyStore(), samlProperties.getKeyStorePassword(),
samlProperties.getKeyPassword());
} catch (Exception e) {
throw new IllegalStateException("Unable to initialize KeyManager with keyStore: " + samlProperties.getKeyStore(), e);
}
} else {
return new EmptyKeyManager();
}
}
@Bean
public TLSProtocolConfigurer tlsProtocolConfigurer(KeyManager keyManager) {
TLSProtocolConfigurer configurer = new TLSProtocolConfigurer();
configurer.setKeyManager(keyManager);
return configurer;
}
}
package com.keymobile.login.saml;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.saml.SAMLBootstrap;
import org.springframework.security.saml.context.SAMLContextProviderImpl;
import org.springframework.security.saml.context.SAMLContextProviderLB;
import org.springframework.security.saml.log.SAMLDefaultLogger;
import org.springframework.security.saml.metadata.CachingMetadataManager;
import org.springframework.security.saml.parser.ParserPoolHolder;
import org.springframework.security.saml.websso.*;
import java.util.List;
@Configuration
public class SAMLConfigDefaults {
@Bean
public static SAMLBootstrap sAMLBootstrap() {
return new SAMLBootstrap();
}
@Bean
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
@Autowired
private SamlProperties samlProperties;
@Bean
public SAMLContextProviderImpl contextProvider() {
if (samlProperties.getUseLB()) {
SAMLContextProviderLB samlContextProviderLB = new SAMLContextProviderLB();
samlContextProviderLB.setScheme(samlProperties.getContext().getSchema());
samlContextProviderLB.setServerName(samlProperties.getContext().getServerName());
samlContextProviderLB.setContextPath(samlProperties.getContext().getContextPath());
samlContextProviderLB.setServerPort(samlProperties.getContext().getServerPort());
samlContextProviderLB.setIncludeServerPortInRequestURL(true);
return samlContextProviderLB;
} else {
return new SAMLContextProviderImpl();
}
}
@Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
@Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
@Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
@Bean
public WebSSOProfileECPImpl ecpProfile() {
return new WebSSOProfileECPImpl();
}
@Bean
public WebSSOProfileHoKImpl hokWebSSOProfile() {
return new WebSSOProfileHoKImpl();
}
@Bean
public SingleLogoutProfile logoutProfile() {
return new SingleLogoutProfileImpl();
}
// 配置 IDP 元数据的 CachingMetadataManager
@Bean
public CachingMetadataManager metadataManager(List<MetadataProvider> metadataProviders) throws MetadataProviderException {
return new CachingMetadataManager(metadataProviders);
}
}
\ No newline at end of file
package com.keymobile.login.saml;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import java.lang.annotation.*;
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface SAMLUser {
}
\ No newline at end of file
package com.keymobile.login.saml;
import org.opensaml.saml2.core.Attribute;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Default Implementation of {@link UserDetails} for Spring Boot Security SAML. This simple implementation hardly covers all security aspects since it's mostly
* hardcoded. I.E. accounts are never locked, expired, or disabled, and always return the same granted authority "ROLE_USER". Consider implementing your own
* {@link UserDetails} and {@link SAMLUserDetailsService}.
*/
public class SAMLUserDetails implements UserDetails {
private SAMLCredential samlCredential;
public SAMLUserDetails(SAMLCredential samlCredential) {
this.samlCredential = samlCredential;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return "";
}
@Override
public String getUsername() {
return samlCredential.getNameID().getValue();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public String getAttribute(String name) {
return samlCredential.getAttributeAsString(name);
}
public String[] getAttributeArray(String name) {
return samlCredential.getAttributeAsStringArray(name);
}
public Map<String, String> getAttributes() {
return samlCredential.getAttributes().stream()
.collect(Collectors.toMap(Attribute::getName, this::getString));
}
private String getString(Attribute attribute) {
String value = getValue(attribute);
return value == null ? "" : value;
}
public Map<String, String[]> getAttributesArrays() {
return samlCredential.getAttributes().stream()
.collect(Collectors.toMap(Attribute::getName, this::getValueArray));
}
private String getValue(Attribute attribute) {
return getAttribute(attribute.getName());
}
private String[] getValueArray(Attribute attribute) {
return getAttributeArray(attribute.getName());
}
}
\ No newline at end of file
package com.keymobile.login.saml;
import com.keymobile.auth.common.security.CustomizedUserDetailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.authentication.CachingUserDetailsService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Service
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(SAMLUserDetailsServiceImpl.class);
@Autowired
private CustomizedUserDetailService customUserDetailService;
@Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
LOGGER.info("Login received for user {}", credential.getNameID().getValue());
UserDetails userDetails = customUserDetailService.loadUserByUsername("root");
// HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// //根据userDetails构建新的Authentication,这里使用了
// PreAuthenticatedAuthenticationToken当然可以用其他token,如UsernamePasswordAuthenticationToken
// PreAuthenticatedAuthenticationToken authentication =
// new PreAuthenticatedAuthenticationToken(userDetails, userDetails.getPassword(),userDetails.getAuthorities());
//设置authentication中details
// UsernamePasswordAuthenticationToken authentication =
// new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
// authentication.setDetails(new WebAuthenticationDetails(request));
// //存放authentication到SecurityContextHolder
// SecurityContextHolder.getContext().setAuthentication(authentication);
// HttpSession session = request.getSession(true);
// //在session中存放security context,方便同一个session中控制用户的其他操作
// session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
//// return userDetails;
// return new SAMLUserDetails(credential);
return userDetails;
}
}
\ No newline at end of file
package com.keymobile.login.saml;
import org.apache.commons.lang3.StringUtils;
import org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider;
import org.opensaml.util.resource.FilesystemResource;
import org.opensaml.util.resource.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.saml.*;
import org.springframework.security.saml.key.EmptyKeyManager;
import org.springframework.security.saml.metadata.MetadataDisplayFilter;
import org.springframework.security.saml.metadata.MetadataGeneratorFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.velocity.app.VelocityEngine;
import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.parse.StaticBasicParserPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.saml.SAMLAuthenticationProvider;
import org.springframework.security.saml.SAMLBootstrap;
import org.springframework.security.saml.SAMLDiscovery;
import org.springframework.security.saml.SAMLEntryPoint;
import org.springframework.security.saml.SAMLLogoutFilter;
import org.springframework.security.saml.SAMLLogoutProcessingFilter;
import org.springframework.security.saml.SAMLProcessingFilter;
import org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter;
import org.springframework.security.saml.context.SAMLContextProviderImpl;
import org.springframework.security.saml.key.JKSKeyManager;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.log.SAMLDefaultLogger;
import org.springframework.security.saml.metadata.CachingMetadataManager;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;
import org.springframework.security.saml.metadata.MetadataDisplayFilter;
import org.springframework.security.saml.metadata.MetadataGenerator;
import org.springframework.security.saml.metadata.MetadataGeneratorFilter;
import org.springframework.security.saml.parser.ParserPoolHolder;
import org.springframework.security.saml.processor.HTTPArtifactBinding;
import org.springframework.security.saml.processor.HTTPPAOS11Binding;
import org.springframework.security.saml.processor.HTTPPostBinding;
import org.springframework.security.saml.processor.HTTPRedirectDeflateBinding;
import org.springframework.security.saml.processor.HTTPSOAP11Binding;
import org.springframework.security.saml.processor.SAMLBinding;
import org.springframework.security.saml.processor.SAMLProcessorImpl;
import org.springframework.security.saml.util.VelocityFactory;
import org.springframework.security.saml.websso.ArtifactResolutionProfile;
import org.springframework.security.saml.websso.ArtifactResolutionProfileImpl;
import org.springframework.security.saml.websso.SingleLogoutProfile;
import org.springframework.security.saml.websso.SingleLogoutProfileImpl;
import org.springframework.security.saml.websso.WebSSOProfile;
import org.springframework.security.saml.websso.WebSSOProfileConsumer;
import org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl;
import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl;
import org.springframework.security.saml.websso.WebSSOProfileECPImpl;
import org.springframework.security.saml.websso.WebSSOProfileImpl;
import org.springframework.security.saml.websso.WebSSOProfileOptions;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.ResourceUtils;
//@Configuration
//@EnableWebSecurity
//@EnableGlobalMethodSecurity(securedEnabled = true)
//@ComponentScan("com.keymobile.auth.common.security")
public class SPWebSecurityConfig extends WebSecurityConfigurerAdapter implements InitializingBean, DisposableBean {
@javax.annotation.Resource
private SpConfig spConfig;
private Timer backgroundTaskTimer;
private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
public void init() {
this.backgroundTaskTimer = new Timer(true);
this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
}
public void shutdown() {
this.backgroundTaskTimer.purge();
this.backgroundTaskTimer.cancel();
this.multiThreadedHttpConnectionManager.shutdown();
}
@javax.annotation.Resource
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
// Initialization of the velocity engine
@Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
// XML parser pool needed for OpenSAML parsing
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
// Bindings, encoders and decoders used for creating and parsing messages
@Bean
public HttpClient httpClient() {
return new HttpClient(this.multiThreadedHttpConnectionManager);
}
// SAML Authentication Provider responsible for validating of received SAML
// messages
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
// Provider of default SAML Context
@Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
}
// Initialization of OpenSAML library
@Bean
public static SAMLBootstrap sAMLBootstrap() {
return new SAMLBootstrap();
}
// Logger for SAML messages and events
@Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
// SAML 2.0 WebSSO Assertion Consumer
@Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 Web SSO profile
@Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
// SAML 2.0 Holder-of-Key Web SSO profile
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 ECP profile
@Bean
public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl();
}
@Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
// sp密钥库
// Central storage of cryptographic keys
// @Bean
// public KeyManager keyManager(KeystoreFactory keystoreFactory) {
// return new EmptyKeyManager();
// }
@Autowired
private SamlProperties samlProperties;
@Bean
public KeystoreFactory keystoreFactory() {
return new KeystoreFactory();
}
@Bean
public KeyManager keyManager(KeystoreFactory keystoreFactory) throws Exception {
if (samlProperties.useCerts()) {
return keystoreFactory.getJKSKeyManager(samlProperties.getPublicKeyCert(), samlProperties.getPrivateKeyCert());
}
if (samlProperties.useKeyStore()) {
try {
return keystoreFactory.getJKSKeyManager(samlProperties.getKeyStore(), samlProperties.getKeyStorePassword(),
samlProperties.getKeyPassword());
} catch (Exception e) {
throw new IllegalStateException("Unable to initialize KeyManager with keyStore: " + samlProperties.getKeyStore(), e);
}
} else {
return new EmptyKeyManager();
}
}
@Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
return webSSOProfileOptions;
}
// Entry point to initialize authentication, default values taken from
// properties file
@Bean
public SAMLEntryPoint samlEntryPoint() {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
return samlEntryPoint;
}
// 扩展元数据
// Setup advanced info about metadata
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(spConfig.getIdpDiscoveryEnable());
extendedMetadata.setSigningAlgorithm(spConfig.getSignAlg());
extendedMetadata.setSignMetadata(spConfig.getSignMetadata());
extendedMetadata.setEcpEnabled(true);
return extendedMetadata;
}
// 服务发现页面地址
// IDP Discovery Service
// @Bean
// public SAMLDiscovery samlIDPDiscovery() {
// SAMLDiscovery idpDiscovery = new SAMLDiscovery();
// idpDiscovery.setIdpSelectionPath(spConfig.getIdpSelectionPath());
// return idpDiscovery;
// }
// @Bean
// @Qualifier("idp-ssocircle")
// public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider()
// throws Exception {
// final File file = ResourceUtils.getFile("classpath:idp-okta.xml");
// org.opensaml.util.resource.Resource idpResource = new FilesystemResource(file.getPath());
// Timer refreshTimer = new Timer(true);
// ResourceBackedMetadataProvider delegate;
// delegate = new ResourceBackedMetadataProvider(refreshTimer, idpResource);
// ExtendedMetadataDelegate extendedMetadataDelegate =
// new ExtendedMetadataDelegate(delegate, extendedMetadata());
// extendedMetadataDelegate.setMetadataTrustCheck(false);
// extendedMetadataDelegate.setMetadataRequireSignature(false);
// backgroundTaskTimer.purge();
// return extendedMetadataDelegate;
// }
@Bean
@Qualifier("idp-ssocircle")
public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider() {
try {
final File file = ResourceUtils.getFile("classpath:idp-mairui.xml");
Resource idpResource = new FilesystemResource(file.getPath());
Timer refreshTimer = new Timer(true);
ResourceBackedMetadataProvider delegate;
delegate = new ResourceBackedMetadataProvider(refreshTimer, idpResource);
delegate.setParserPool(parserPool());
ExtendedMetadata extendedMetadata = extendedMetadata().clone();
ExtendedMetadataDelegate provider = new ExtendedMetadataDelegate(delegate, extendedMetadata);
provider.setMetadataTrustCheck(true);
provider.setMetadataRequireSignature(false);
String idpName = file.getName().replaceAll(".xml", "");
extendedMetadata.setAlias(idpName);
// 配置 IDP 元数据的 provider
return provider;
} catch (Exception e) {
throw new IllegalStateException("Unable to initialize IDP Metadata", e);
}
}
// IDP Metadata configuration - paths to metadata of IDPs in circle of trust
// is here
// Do no forget to call iniitalize method on providers
@Bean
@Qualifier("metadata")
public CachingMetadataManager metadata() throws Exception {
List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
providers.add(ssoCircleExtendedMetadataProvider());
return new CachingMetadataManager(providers);
}
// 元数据生成bean
// Filter automatically generates default SP metadata
@Bean
public MetadataGenerator metadataGenerator() throws Exception {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(spConfig.getEntityId());
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager(new KeystoreFactory()));
metadataGenerator.setRequestSigned(false);
metadataGenerator.setWantAssertionSigned(spConfig.getWantAssertionSigned());
return metadataGenerator;
}
// The filter is waiting for connections on URL suffixed with filterSuffix
// and presents SP metadata there
@Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
// 设置登陆成功后的重定向地址,或者说是首页地址
// Handler deciding where to redirect user after successful login
@Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl(spConfig.getSuccessLoginUrl());
return successRedirectHandler;
}
// Handler deciding where to redirect user after failed login
@Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler =
new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl(spConfig.getFailLoginUrl());
return failureHandler;
}
@Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOHoKProcessingFilter;
}
// Processing filter for WebSSO profile messages
@Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter() throws Exception {
return new MetadataGeneratorFilter(metadataGenerator());
}
// Handler for successful logout
@Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl(spConfig.getSuccessLogoutUrl());
return successLogoutHandler;
}
// Logout handler terminating local session
@Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler =
new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
// Filter processing incoming logout messages
// First argument determines URL user will be redirected to after successful
// global logout
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(),
logoutHandler());
}
// Overrides default logout processing filter with the one processing SAML
// messages
@Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[]{logoutHandler()},
new LogoutHandler[]{logoutHandler()});
}
// Bindings
private ArtifactResolutionProfile artifactResolutionProfile() {
final ArtifactResolutionProfileImpl artifactResolutionProfile =
new ArtifactResolutionProfileImpl(httpClient());
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding()));
return artifactResolutionProfile;
}
@Bean
public HTTPArtifactBinding artifactBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile());
}
@Bean
public HTTPSOAP11Binding soapBinding() {
return new HTTPSOAP11Binding(parserPool());
}
@Bean
public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), velocityEngine());
}
@Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool());
}
@Bean
public HTTPSOAP11Binding httpSOAP11Binding() {
return new HTTPSOAP11Binding(parserPool());
}
@Bean
public HTTPPAOS11Binding httpPAOS11Binding() {
return new HTTPPAOS11Binding(parserPool());
}
// Processor
@Bean
public SAMLProcessorImpl processor() {
Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
bindings.add(artifactBinding(parserPool(), velocityEngine()));
bindings.add(httpSOAP11Binding());
bindings.add(httpPAOS11Binding());
return new SAMLProcessorImpl(bindings);
}
/**
* Define the security filter chain in order to support SSO Auth by using SAML 2.0
*
* @return Filter chain proxy
* @throws Exception
*/
@Bean
public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
samlLogoutFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
samlWebSSOProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter()));
// chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
// samlIDPDiscovery()));
return new FilterChainProxy(chains);
}
/**
* Returns the authentication manager currently used by Spring.
* It represents a bean definition with the aim allow wiring from
* other classes performing the Inversion of Control (IoC).
*
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* Defines the web based security configuration.
*
* @param http It allows configuring web based security for specific http requests.
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.authenticationEntryPoint(samlEntryPoint());
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(samlFilter(), CsrfFilter.class);
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/saml/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/js/**").permitAll()
.anyRequest().authenticated();
http
.logout()
.disable(); // The logout procedure is already handled by SAML filters.
}
/**
* Sets a custom authentication provider.
*
* @param auth SecurityBuilder used to create an AuthenticationManager.
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(samlAuthenticationProvider());
}
@Override
public void afterPropertiesSet() throws Exception {
init();
}
@Override
public void destroy() throws Exception {
shutdown();
}
}
package com.keymobile.login.saml;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "demo.saml")
@Component
public class SamlProperties {
/**
* idp 元数据 xml 所在位置
*/
private String idpXml;
/**
* 用于验证数字签名的公钥证书所在位置,X509 格式
*/
private String publicKeyCert;
/**
* 用于数字签名的私钥所在位置,RSA 算法类型,PKCS8 格式
*/
private String privateKeyCert;
/**
* 包含用于验证数字签名的公钥证书,用于数字签名的私钥 的 JKS 所在位置
* 需要清楚传入的 JKS 中私钥的别名。
*/
private String keyStore;
/**
* {@link #keyStore} 的密码
*/
private char[] keyStorePassword;
/**
* {@link #privateKeyCert} 或 {@link #keyStore} 的私钥密码
*/
private char[] keyPassword;
//配置本身服务相关信息
@Autowired
private ContextProperties context;
private String successTargetUrl;
private Boolean useLB;
private String entityId;
private Boolean wantAssertionSigned;
private Boolean signMetadata;
public Boolean getWantAssertionSigned() {
return wantAssertionSigned;
}
public void setWantAssertionSigned(Boolean wantAssertionSigned) {
this.wantAssertionSigned = wantAssertionSigned;
}
public Boolean getSignMetadata() {
return signMetadata;
}
public void setSignMetadata(Boolean signMetadata) {
this.signMetadata = signMetadata;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public String getEntityId() {
return entityId;
}
public void setUseLB(Boolean useLB) {
this.useLB = useLB;
}
public Boolean getUseLB() {
return useLB;
}
public void setSuccessTargetUrl(String successTargetUrl) {
this.successTargetUrl = successTargetUrl;
}
public String getSuccessTargetUrl() {
return successTargetUrl;
}
public void setContext(ContextProperties context) {
this.context = context;
}
public ContextProperties getContext() {
return context;
}
public boolean useKeyStore() {
return StringUtils.isNotBlank(keyStore);
}
public boolean useCerts() {
return StringUtils.isNotBlank(publicKeyCert) && StringUtils.isNotBlank(privateKeyCert);
}
public String getIdpXml() {
return idpXml;
}
public void setIdpXml(String idpXml) {
this.idpXml = idpXml;
}
public String getPublicKeyCert() {
return publicKeyCert;
}
public void setPublicKeyCert(String publicKeyCert) {
this.publicKeyCert = publicKeyCert;
}
public String getPrivateKeyCert() {
return privateKeyCert;
}
public void setPrivateKeyCert(String privateKeyCert) {
this.privateKeyCert = privateKeyCert;
}
public String getKeyStore() {
return keyStore;
}
public void setKeyStore(String keyStore) {
this.keyStore = keyStore;
}
public char[] getKeyStorePassword() {
return keyStorePassword;
}
public void setKeyStorePassword(char[] keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public char[] getKeyPassword() {
return keyPassword;
}
public void setKeyPassword(char[] keyPassword) {
this.keyPassword = keyPassword;
}
}
package com.keymobile.login.saml;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "sp")
public class SpConfig {
private String idpMetadataUrl;
private String entityId;
private Boolean wantAssertionSigned;
private Boolean signMetadata;
private String signAlg;
private Boolean idpDiscoveryEnable;
private String IdpSelectionPath;
private String successLoginUrl;
private String failLoginUrl;
private String successLogoutUrl;
private JKS jks;
@Data
public class JKS {
private String path;
private String password;
private String defaultKey;
}
}
\ No newline at end of file
package com.keymobile.login.saml;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.saml.*;
import org.springframework.security.saml.metadata.MetadataDisplayFilter;
import org.springframework.security.saml.metadata.MetadataGeneratorFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
//@Configuration
//@EnableWebSecurity
//@EnableGlobalMethodSecurity(securedEnabled = true)
//@ComponentScan("com.keymobile.auth.common.security")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SAMLLogoutFilter samlLogoutFilter;
@Autowired
private SAMLLogoutProcessingFilter samlLogoutProcessingFilter;
@Autowired
private MetadataDisplayFilter metadataDisplayFilter;
@Autowired
private MetadataGeneratorFilter metadataGeneratorFilter;
@Autowired
private SAMLProcessingFilter samlWebSSOProcessingFilter;
@Autowired
private SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter;
@Autowired
private SAMLEntryPoint samlEntryPoint;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void init(WebSecurity web) throws Exception {
super.init(web);
}
/**
* Defines the web based security configuration.
*
* @param http It allows configuring web based security for specific http requests.
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
HttpSessionSecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
securityContextRepository.setSpringSecurityContextKey("SPRING_SECURITY_CONTEXT_SAML");
http
.securityContext()
.securityContextRepository(securityContextRepository);
http
.httpBasic()
.disable();
http
.csrf()
.disable();
http
.addFilterAfter(metadataGeneratorFilter, BasicAuthenticationFilter.class)
.addFilterAfter(metadataDisplayFilter, MetadataGeneratorFilter.class)
.addFilterAfter(samlEntryPoint, MetadataDisplayFilter.class)
.addFilterAfter(samlWebSSOProcessingFilter, SAMLEntryPoint.class)
.addFilterAfter(samlWebSSOHoKProcessingFilter, SAMLProcessingFilter.class)
.addFilterAfter(samlLogoutProcessingFilter, SAMLWebSSOHoKProcessingFilter.class)
// .addFilterAfter(samlIDPDiscovery, SAMLLogoutProcessingFilter.class)
.addFilterAfter(samlLogoutFilter, LogoutFilter.class);
http
.authorizeRequests()
.antMatchers("/", "/error", "/saml/**").permitAll()
// .antMatchers("/", "/error", "/saml/**", "/idpselection").permitAll()
.anyRequest().authenticated();
http
.exceptionHandling()
.authenticationEntryPoint(samlEntryPoint);
http
.logout()
.disable();
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return authenticationManager;
}
}
\ No newline at end of file
package com.keymobile.login.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);
}
...@@ -26,20 +26,6 @@ spring: ...@@ -26,20 +26,6 @@ spring:
multipart: multipart:
max-file-size: 100Mb max-file-size: 100Mb
max-request-size: 100Mb max-request-size: 100Mb
# security:
# oauth2:
# client:
# registration:
# mairay:
# provider: mairay
# client-id: cfe5edd2-c5cb-422b-86e3-adf50d42d9e6
# authorization-grant-type: authorization_code
# redirect-uri-template: http://my-redirect-uri.com
# provider:
# mairay:
# authorization-uri: https://adfsforms.mindray.com/adfs/oauth2/authorize
# token-uri: https://adfsforms.mindray.com/adfs/oauth2/token
# user-info-uri: https://adfsforms.mindray.com/adfs/oauth2/token
eureka: eureka:
client: client:
...@@ -55,77 +41,17 @@ redirect-url: ...@@ -55,77 +41,17 @@ redirect-url:
#80600793 H?hVm0jn #80600793 H?hVm0jn
#80600745 Wenhua@015 #80600745 Wenhua@015
demo:
saml:
idp-xml: idp-mairui.xml
# idp-xml: idp-okta.xml
entityId: https://localhost:8082
# entityId: lanminwu:mairui:saml:demo
# 是否签名断言,则需要在idp上传sp的证书/公钥文件以供解密
wantAssertionSigned: false
# 是否签名元数据
signMetadata: false
#publickeyCert: classpath:localhost.cert
#privateKeyCert: classpath:localhost.key.der
publickeyCert: localhost.cert
privateKeyCert: localhost.key.der
keyPassword:
successTargetUrl: http://localhost:8089/center-home/menu/index
useLB: false
context:
provider:
schema: http
serverName: localhost
contextPath: /api/auth
serverPort: 8089
#security:
sp: # oauth2:
# 认证中心服务信息 -> IDP元数据URL # client:
idpMetadataUrl: http://localhost:8080/gc-starter-ac/idp/metadata # client-id: javaboy
# entityId,服务提供商唯一标识 # client-secret: 123
entityId: https://localhost:8082 # access-token-uri: http://localhost:1111/oauth/token #获取token地址
# 是否签名断言,则需要在idp上传sp的证书/公钥文件以供解密 # user-authorization-uri: http://localhost:1111/oauth/authorize #认证地址
wantAssertionSigned: false # redirect-uri: http://localhost:8089/api/auth/login #系统首页登录地址
# 是否签名元数据 # authorization-success-redirect-uri: http://localhost:8089/center-home/menu/index #认证成功后跳转地址
signMetadata: false # authorization-login-out-uri: http://localhost:1111/signout
# 签名算法
signAlg: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
# 是否启用服务发现。一个sp可以配置多个idp,启动服务发现允许进入idp选择页面选择idp,如果不启用的话默认使用idp列表的第一个
idpDiscoveryEnable: false
# 服务发现选择页面路由
IdpSelectionPath: /saml/discovery
# idp登录成功后的重定向的页面路由,也就是首页路由
successLoginUrl: http://localhost:8089/center-home/menu/index
# idp登录失败后的重定向的页面路由
failLoginUrl: /error
# 登出成功后跳转的页面路由
successLogoutUrl: /
# 密钥库设置
jks:
# jks文件位置
path: classpath:/saml/samlKeystore.jks
# jks密码
password: nalle123
# 私钥别名
defaultKey: apollo
security:
oauth2:
# sso:
# login-path: http://localhost:8089/api/auth/login
client:
pre-established-redirect-uri: http://localhost:8089/api/auth/login
registered-redirect-uri: http://localhost:8089/api/auth/login
use-current-uri: false
client-id: javaboy
client-secret: 123
access-token-uri: http://localhost:1111/oauth/token #获取token地址
user-authorization-uri: http://localhost:1111/oauth/authorize #认证地址
resource:
user-info-uri: http://localhost:1111/user #获取当前用户信息地址
#security: #security:
# oauth2: # oauth2:
...@@ -140,6 +66,20 @@ security: ...@@ -140,6 +66,20 @@ security:
# resource: # resource:
# user-info-uri: https://adfsforms.mindray.com/adfs/oauth2/token #获取当前用户信息地址 # user-info-uri: https://adfsforms.mindray.com/adfs/oauth2/token #获取当前用户信息地址
security:
oauth2:
client:
client-id: cfe5edd2-c5cb-422b-86e3-adf50d42d9e6
client-secret:
access-token-uri: https://adfsforms.mindray.com/adfs/oauth2/token #获取token地址
user-authorization-uri: https://adfsforms.mindray.com/adfs/oauth2/authorize #认证地址
authorization-success-redirect-uri: http://localhost:8089/center-home/menu/index #认证成功后跳转地址
authorization-login-out-uri: https://adfsforms.mindray.com/adfs/oauth2/logout
post-login-redirect-uri: http://localhost:8089/api/auth/login #adfs登录后回调系统的登录接口
post-logout-redirect_uri: http://localhost:8089/api/auth/signout #adfs退出后回调系统的注销接口
feign:
authUser: root
authPwd: pwd
......
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor entityID="http://adfsforms.mindray.com/adfs/services/trust"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<!--<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>
MIIC7DCCAdSgAwIBAgIQPl0hwi9BYq9ESyTRy0sK0jANBgkqhkiG9w0BAQsFADAyMTAwLgYDVQQDEydBREZTIEVuY3J5cHRpb24gLSBhZGZzZm9ybXMubWluZHJheS5jb20wHhcNMjExMTIwMDAxMDQ3WhcNMjYxMTI0MDAxMDQ3WjAyMTAwLgYDVQQDEydBREZTIEVuY3J5cHRpb24gLSBhZGZzZm9ybXMubWluZHJheS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>-->
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>
MIIC5jCCAc6gAwIBAgIQY0knaAyHd6dFrQMZlL1gYjANBgkqhkiG9w0BAQsFADAvMS0wKwYDVQQDEyRBREZTIFNpZ25pbmcgLSBhZGZzZm9ybXMubWluZHJheS5jb20wHhcNMjExMTIwMDAxMDQ3WhcNMjYxMTI0MDAxMDQ3WjAvMS0wKwYDVQQDEyRBREZTIFNpZ25pbmcgLSBhZGZzZm9ybXMubWluZHJheS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvASvpK
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://adfsforms.mindray.com/adfs/ls/"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://adfsforms.mindray.com/adfs/ls/"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://adfsforms.mindray.com/adfs/ls/"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://adfsforms.mindray.com/adfs/ls/"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor entityID="http://www.okta.com/exk67l5t6p2UNYn6l5d7" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"><md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIIDqDCCApCgAwIBAgIGAYKxtQdnMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
MBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi05NTA5MDU0OTEcMBoGCSqGSIb3DQEJ
ARYNaW5mb0Bva3RhLmNvbTAeFw0yMjA4MTgxNjA1NDdaFw0zMjA4MTgxNjA2NDdaMIGUMQswCQYD
VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsG
A1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi05NTA5MDU0OTEc
MBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALDFFJaTplxbuSYGLSp++TXBQWX9t7MHOR+INrUrxamxhH+wIfe3iJE5A3PvCEXI26abEzCi
MZjv3DTTuSLV6WLH+l11KulFo2j5vRuFFAkGeHy3diM+1z8P1jUGhEaCYvmmiXMe7aFBSNKCI7J2
LjxxI+slv7lQ/tnYcROoXxd3Y4U8KkveymXahMCW5NEKbsRY0b6ASk+CwRDAAmG/R3cn7PMLsUu+
iWZPwFwXN1bSnnaVu4a7sQi4B1SQxiySHBUhJUoi1Udicj8k6cceaoUHxRQEM/CfJiexqdyvdtfm
DdXxFN5BhzGSf8ZPC/Ewd+Ie4wLg1iVqHdnWEDl+h8kCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
huWqwM8Mgn9MHZNdbbTKKI7UqeQ7zPU4aeCoBMOSFyU+/XrlbFq4KUJvnLq8W1DQSLbxSIZbQTdT
al2aDAp3OGp/yxMN9JaMlZsVJf9QpHWX3SL0zwnr/N1lSlzP43T13a6kENhCeBjs24iBKafyH1ZG
fP6+UQxCVdYyngRKiMKRJmnNf4g5n2i27CMKk+zPTYwVMOzbDQDOTdEHU3u524XwyDDpfC2Nzoll
ZyH4lZCjfCQPwm0MCKK+TC8hXiSZ1dMVDTDQ3n+MSmJbgul6TQXX1JCLKYu3Xbj+X1DLjvHgcSa+
gucO7vek44zuQ6NlJiCBNZ8HK75ZnBfbfMe/JQ==</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://dev-95090549.okta.com/app/dev-95090549_mairuisamldemo_1/exk67l5t6p2UNYn6l5d7/slo/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://dev-95090549.okta.com/app/dev-95090549_mairuisamldemo_1/exk67l5t6p2UNYn6l5d7/slo/saml"/><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://dev-95090549.okta.com/app/dev-95090549_mairuisamldemo_1/exk67l5t6p2UNYn6l5d7/sso/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://dev-95090549.okta.com/app/dev-95090549_mairuisamldemo_1/exk67l5t6p2UNYn6l5d7/sso/saml"/></md:IDPSSODescriptor></md:EntityDescriptor>
\ No newline at end of file
-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIJAIU7CnmezGizMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0xNjA0MDMwMjEwMjVaFw0yNjA0MDEwMjEwMjVaMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAN1NO3L/yCCb+MYFkypvJXcjlQuyRG7UFATYOYQZzIxsD9AtnXPh67uVkZTI
oK7Ps5X4a5qVARtdN+GCFZ/ITahlAlIx8rmVsbz+7XPWpGPf75tKbem3pON2NlYW
wIEQqyuValZHDUMgIXPdGIAZeNejVu7gYMLJwiSMtB0uBM69ptzgigJcbnup/cSL
W4fBh4ck5kj0SVmX58knfaizrVf+ghGyNFha9Xy+DoilCofxwFIpVskv/hczZ5L+
e81R+u2UbNzRwf8paF5fdVwaHPGLOYSBGjSm71VDdJqlvKrJCBoCQODhtmJOmDHD
jtf6gwwbdg3g9GvyqIJnRqBO908CAwEAAaNQME4wHQYDVR0OBBYEFMNtl5fAchs3
5gZS4EF8/0C7QfBQMB8GA1UdIwQYMBaAFMNtl5fAchs35gZS4EF8/0C7QfBQMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAADVL8LgYGlmaHlyrKyKfsQF
TVbdT1Fk3WaGocVbhmvFeEBHScSJNR0syDcDM1C18pZ6Jc73cW7UdtLbLbRNPXS+
qcp5GZroafndPIL2QzdKXfc5MiGH7CRCZit9kiNJ6YYgsztappXnwKblioJHB1Bc
oLRzMeD295DAGLEVuc5tSY7JHBD3YQS9Pwt3ivrvvCzFKOU9nHqChMCplO4StGpS
bbSR6XNgsPA0XLWlleuTqLGvJ4bHXPKC+0Y+0AiQYx3GeWLVrwJ4w+PFEK73vyuB
9H10x+zy1nFWvqoa+K66EA4u7DpEoHJBlqH0AVWAd8q9488DpCo1x4ujTGw7AHE=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA3U07cv/IIJv4xgWTKm8ldyOVC7JEbtQUBNg5hBnMjGwP0C2d
c+Hru5WRlMigrs+zlfhrmpUBG1034YIVn8hNqGUCUjHyuZWxvP7tc9akY9/vm0pt
6bek43Y2VhbAgRCrK5VqVkcNQyAhc90YgBl416NW7uBgwsnCJIy0HS4Ezr2m3OCK
Alxue6n9xItbh8GHhyTmSPRJWZfnySd9qLOtV/6CEbI0WFr1fL4OiKUKh/HAUilW
yS/+FzNnkv57zVH67ZRs3NHB/yloXl91XBoc8Ys5hIEaNKbvVUN0mqW8qskIGgJA
4OG2Yk6YMcOO1/qDDBt2DeD0a/KogmdGoE73TwIDAQABAoIBAQDG0OH9+OnU0gt3
6/5A+0XPeTooHen5H7M0fwV9Nqhb56F1R+XS/D8Kcd8uqegh5RvUOjCB2if6a48O
nA3NVOjfxo+FRLZqIKBjySuPDGD4EXF0NDP26zPJ3qQGR75+tXjyWPQFuyOhELa9
Hv8p5rh4EpjBVvfXR+eRao9OP8+142Sedn5yKobQ6Qe9h4TIgBoCi1XCs86ySChr
7ITG3vEeB3Eq5hSbeN255m+VGjDySeeXsKHmnDHEq0ayLa8LbmD0XLmiLTrbV8IO
Zy7qDxxnlIxjAmmbQj42orNNhDQwoGzy1wa8Yh5/3gwdxGXhufo7ItkkeRnCKCmh
ThP0GSqBAoGBAPZNtNEHDxo5Getm4DbywTHYv+XBlWfLDkJSRjMSTCKzv9MemoX4
vE5sx4ax8oLRcz4pQnfbXQeDePvvLyWApS6qhWbS9qf8oaIt2exPolqmCJibdGpM
Y0Xk3ZFpUA7a+2jPK1PbrwLj/w7zNmFU8Rh/8RZS0QmBIcxovPH7kZ7vAoGBAOYD
jXGVxcxsTdK8Ns6hltVPEE+XmWGfInCVfpAvECN+plwna1abdXafYnMXlmg/tC4q
2Ufkcw3PEtFKCAMQ1P0n8jSKXpjj1Lxlrunj2404eH93uHJih6zYE2gb/P+7jm49
377SQOObrueWZUy4mIIZmbT6464rq6sKP6tb1i2hAoGBAIQG31f0yrmpxiUTPjj2
I21O3H6SKD488GXIqGyT8E/hvn+yte3+iSIY2VNwa6iIEZhOkZyh79opNV8GtWUK
8oBzU5Lsnt8pYpMGtPwhK8wfmBgFrH+Wdthud/6MTyfHZmCmPHl1FvkbsgsXgBzo
ZVxWqKrotbi8iZuCwVWNHl/tAoGAEms2aGIV9Mi3cqifuuw1p98s7zK0lZyopVtT
Rzh9kloR+E8vyT+pqFYbDBxXbwGq7AeCXr9sdy6d0ySaf6RZaexI+OwbpyKXZn6+
Avy8GBLtk0eC/aXmN3EWHMAhAlmCjlFmGWG80H0nBGSGuB4QGFr0dAmjMc9Nb+Ti
NFamUAECgYEA7uPN2cpwvVlt4Q4BHxjvvo1XDRe5c+RwDVa/dYkLu85HVvRd42SJ
QgIdcZR9kLRXuOFCWv1shFmfHPqfmH0q6hn39TgVnUyHxzBlAbP5Dx/n4FRs8EP8
qqNS0ft5Mqoy1UuJL6N5iDYY+i8Is90bsC9mMPj007kfFcyJcRULrSA=111
-----END RSA PRIVATE KEY-----
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