SpringSecurity实战笔记之OAuth

发布时间 2023-08-21 11:39:37作者: 咔咔皮卡丘

===================Spring Social OAuth================


一、app、小程序、前后端分离为什么使用OAuth协议


1、原有方法开发繁琐、安全性和客户体验差、有些前端技术不支持cookei,如小程序
2、好处:token自动生成,自定义校验,方便安全


二、Spring Security OAuth简介


1、服务提供商(Provider)提供认证服务器(Anthorization Server)及资源服务器(Resource Server)


2、认证服务器:包含四种授权模式(用户可以自定义认证方式)->生成存储Token


3、资源服务器:通过Spring Security过滤器链(添加OAuth2Authentication ProcessingFilter)保护资源
OAuth2Authentication ProcessingFilter 从请求中拿到Token,再通过指定的策略从token中读取用户相应的信息,再判断是否有权限读取资源


三、实现标准的OAuth服务提供商(查看框架https://oauth.net/2/ 页面中OAuth 2.0 Framework)


1、认证服务器

/**
* 认证服务器,只需要添加@EnableAuthorizationServer
*/
@Configuration
@EnableAuthorizationServer
public class ImoocAuthorizationServerConfig {

}

 

1.1、授权码模式(两步)
请求codeUrl /oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=all
return: http://example.com/?code=NZMOnL

请求tokenUrl method:POST /oauth/token
参数:

headers:
Content-Type:application/x-www-form-urlencoded
Authorization:Basic aW1vb2M6aW1vb2NzZWNyZXQ=
body:
grant_type:authorization_code
code:NZMOnL
redirect_uri:http://example.com
client_id:imooc
scope:all
return:
{
"access_token": "6a06dc0c-3e2c-4179-b1ee-7878a84b4f82",
"token_type": "bearer",
"refresh_token": "317b6725-593c-45e5-9aa1-fbe1b82172f0",
"expires_in": 43199,
"scope": "all"
}

 


1.2、密码模式(一步,适用于内部使用)
请求tokenUrl method:POST /oauth/token
参数:

headers:
Content-Type:application/x-www-form-urlencoded
Authorization:Basic aW1vb2M6aW1vb2NzZWNyZXQ=
body:
grant_type:password
username:user
password:123456
scope:all
return:
{
"access_token": "6a06dc0c-3e2c-4179-b1ee-7878a84b4f82",
"token_type": "bearer",
"refresh_token": "317b6725-593c-45e5-9aa1-fbe1b82172f0",
"expires_in": 42656,
"scope": "all"
}

 

2、资源服务器

/**
* 资源服务器
*/
@Configuration
@EnableResourceServer
public class ImoocResourceServerConfig {

}

 

2.1、请求用户信息

url:/user/me
hearders:
Authorization:bearer 712a314c-4aef-46cf-8eb9-3269a3c554f0
return:
{
"password": null,
"username": "user",
"authorities": [
{
"authority": "ROLE_USER"
},
{
"authority": "admin"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"userId": "user"
}

注意点:

-用户必须有ROLE_USER角色才能访问
-clientId,clientSecret默认启动时分配,但可以配置

security.oauth2.client.client-id=imooc
security.oauth2.client.client-secret=imoocsecret

 

四、SpringSecurityOAuth核心源码解析

1、流程:获取令牌的请求(/oauth/token)
->TokenEndpoint
->ClientDetailsService(InMemoryClientDetailsService)
->ClientDetails
->TokenRequest
->TokenGranter(CompositeTokenGranter)
->OAuth2Request&Authentication
->OAuth2Authentication
->AuthorizationServerTokenServices(DefaultTokenServices)[TokenStore][TokenEnhancer]
->OAuth2AccessToken


五、重构3种登录


1、流程:登录请求
->XXXFilter
->登录逻辑处理
->AuthenticationSuccessHandler(以下功能在这个类中实现)
->OAuth2Request(ClientDetails(ClientDetailsService(ClientId)) 、TokenRequest(request))&Authentication
->OAuth2Authentication
->AuthorizationServerTokenServices(DefaultTokenServices)[TokenStore][TokenEnhancer]
->OAuth2AccessToken
2、OAuth2Request构造
2.1、ClientId->ClientDetailsService->ClientDetails&TokenRequest(request)
2.2、ClientId获取:(参照BasicAuthenticationFilter)从请求头部信息Authorization中获取Basic aW1vb2M6aW1vb2NzZWNyZXQ=
2.3、代码
//1、获取clientId
String clientId = tokens[0];
String clientSecret = tokens[1];
//2、获取ClientDetails
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
//3、获取TokenRequest
if(clientDetails == null){
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:"+clientId);
}else if(!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
throw new UnapprovedClientAuthenticationException("clientSecret不匹配:"+clientId);
}
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
//4、创建OAuth2Request
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

3、OAuth2Authentication构造
//5、创建OAuth2Authentication
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
4、安全配置
/**
* 资源服务器
*/
@Configuration
@EnableResourceServer
public class ImoocResourceServerConfig extends ResourceServerConfigurerAdapter{
@Autowired
protected AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
protected AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Autowired
private SecurityProperties securityProperties;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private SpringSocialConfigurer imoocSocialSecurityConfig;
@Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
.successHandler(imoocAuthenticationSuccessHandler)
.failureHandler(imoocAuthenticationFailureHandler);

http
/* .apply(validateCodeSecurityConfig)
.and()*/
.apply(smsCodeAuthenticationSecurityConfig)
.and()
.apply(imoocSocialSecurityConfig)
.and()
.authorizeRequests()
.antMatchers(
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
securityProperties.getBrowser().getSignInPage(),
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
securityProperties.getBrowser().getSignUpUrl(),
securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
securityProperties.getBrowser().getSignOutUrl(),
"/user/regist"
).permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
;
}
}
5、登录成功后请求资源
资源url:http://{{ip:port}}/user/me
headers:Authorization:bearer 80aaa952-230a-40a8-816b-c820c0100f5c(token)

 

六、重构验证码存储逻辑(同时解决了短信验证码登录及表单验证码登录)


1、之前短信验证图与图片验证码是存放在session中的,现在就不适用了
2、改为存放在redis中,生成时带上deviceId(存),校验时也带上deviceId(取)
接口:ValidateCodeRepository
browser中的实现类:SessionValidateCodeRepository
app中的实现类:RedisValidateCodeRepository


七、重构第三方登录


1、app通过openId(app通过简化模式获得openId)请求token
1.1、OpenIdAuthenticationToken
1.2、OpenIdAuthenticationFilter
1.3、OpenIdAuthenticationProvider
1.4、OpenIdAuthenticationSecurityConfig
1.5、将OpenIdAuthenticationSecurityConfig apply到ImoocResourceServerConfig中
1.6、登录
请求tokenUrl method:POST /authentication/openid
参数:
headers:
Content-Type:application/x-www-form-urlencoded
Authorization:Basic aW1vb2M6aW1vb2NzZWNyZXQ=
body:
openId:password
providerId:8349FC568B3A166DC4FEB6EC934145F0
password:qq
return:
{
"access_token": "6a06dc0c-3e2c-4179-b1ee-7878a84b4f82",
"token_type": "bearer",
"refresh_token": "317b6725-593c-45e5-9aa1-fbe1b82172f0",
"expires_in": 42656,
"scope": "all"
}
2、app通过第三方code(app通过授权模式获得code)请求token

2.1、直接将第三方认证返回的带有code的url重定向到后台
http://www.pinzhi365.com/auth/weixin?code=0712E5f01GSZHZ11nsg01PgSe012E5fz&state=606d9f50-2dae-45fb-93e1-cc3a232543b7
2.2、后台就会接着到换取token.............
2.3、成功后,要进入到APP中的成功处理器中
ImoocSpringSocialConfigurer中filter要指定成功处理器为APP的成功处理器


八、重构注册逻辑


1、浏览器环境下,在用户第一次登录的情况下,会将用户的第三方信息保存在session中,再跳转到注册页面,
用户注册时再通过providerSignUtils工具的doPostSignUp(userId,new ServletRequest(request))进行绑定
2、app中的解决思路就是,在用户第一次登录的情况下,会将用户的第三方信息保存在redis(key中加入设备id),然后通知前端引导用户去注册,
用户注册时再通过自定义的AppSignUtils工具的doPostSignUp(userId,new ServletRequest(request))进行绑定
3、更改注册路由
/**
* @author zhailiang
* 实现BeanPostProcessor,可以在Bean初始化之前与初始化之后进行操作
*
*/
@Component
public class SpringSocialConfigurerPostProcessor implements BeanPostProcessor {

/* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

/* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(StringUtils.equals(beanName, "imoocSocialSecurityConfig")){
ImoocSpringSocialConfigurer config = (ImoocSpringSocialConfigurer)bean;
config.signupUrl(SecurityConstants.DEFAULT_SOCIAL_USER_INFO_URL);
return config;
}
return bean;
}

}

4、AppSignUtils代码
@Component
public class AppSignUpUtils {

@Autowired
private RedisTemplate<Object,Object> redisTemplate;

@Autowired
private ConnectionFactoryLocator connectionFactoryLocator;

@Autowired
private UsersConnectionRepository usersConnectionRepository;

public void sveConnectionData(WebRequest request, ConnectionData connectionData){
redisTemplate.opsForValue().set(getKey(request),connectionData,10, TimeUnit.MINUTES);
}

/**
* 缓存社交网站用户信息到redis
* @param request
* @param connectionData
*/
public void saveConnectionData(WebRequest request, ConnectionData connectionData) {
redisTemplate.opsForValue().set(getKey(request), connectionData, 10, TimeUnit.MINUTES);
}

/**
* 将缓存的社交网站用户信息与系统注册用户信息绑定
* @param request
* @param userId
*/
public void doPostSignUp( String userId,WebRequest request) {
String key = getKey(request);
if(!redisTemplate.hasKey(key)){
throw new AppSecretException("无法找到缓存的用户社交账号信息");
}
ConnectionData connectionData = (ConnectionData) redisTemplate.opsForValue().get(key);
Connection<?> connection = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId())
.createConnection(connectionData);
usersConnectionRepository.createConnectionRepository(userId).addConnection(connection);
redisTemplate.delete(key);
}

private String getKey(WebRequest request) {
String deviceId = request.getHeader("deviceId");
if(StringUtils.isBlank(deviceId)){
throw new AppSecretException("设备Id参数不能为空");
}
return "imooc:security:social.connect." + deviceId;
}
}
5、AppSecurityController代码
@RestController
public class AppSecurityController extends SocialController{

@Autowired
private ProviderSignInUtils providerSignInUtils;

@Autowired
private AppSignUpUtils AppSignUpUtils;

/**
* 需要注册时跳到这里,返回401和用户信息给前端
* @param request
* @return
*/
@GetMapping(SecurityConstants.DEFAULT_SOCIAL_USER_INFO_URL)//要进行注册时跳转的url
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public SocialUserInfo getSocialUserInfo(HttpServletRequest request) {
Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
AppSignUpUtils.saveConnectionData(new ServletWebRequest(request), connection.createData());
return buildSocialUserInfo(connection);
}
}
6、注册controller
@RequestMapping("/user")
public class UserController {


@Autowired
private AppSignUpUtils appSignUpUtils;

//更新imooc_userconnection
@PostMapping("/regist")
public Object regist(User user,HttpServletRequest request){
//不管是注册用户还是绑定用户,都会拿到用户的唯一标识
String userName = user.getUsername();

//浏览器
// providerSignInUtils.doPostSignUp(userName,new ServletWebRequest(request));

//App
appSignUpUtils.doPostSignUp(userName,new ServletWebRequest(request));
return user;
}
}


九、令牌配置


1、ImoocAuthorizationServerConfig 继承 AuthorizationServerConfigurerAdapter进行相应的配置
-重写相应的方法
/**
* tokenKey的访问权限表达式配置
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
/**
* 客户端配置
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
for (OAuth2ClientProperties client : securityProperties.getOauth2().getClients()) {
builder.withClient(client.getClientId())
.secret(client.getClientSecret())
.authorizedGrantTypes("refresh_token", "authorization_code", "password")
.accessTokenValiditySeconds(client.getAccessTokenValidateSeconds())
.refreshTokenValiditySeconds(2592000)
.scopes("all");
}
}
}

/**
* 认证及token配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
2、定义tokenStore
@Configuration
public static class RedisConfig {

@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* @return
*/
@Bean
public TokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}

}
3、属性类:OAuth2Properties、OAuth2ClientProperties

4、application.properties
#认证服务器注册的第三方应用配置项,参见OAuth2ClientProperties
imooc.security.oauth2.clients[0].clientId = imooc
imooc.security.oauth2.clients[0].clientSecret = imoocsecret
imooc.security.oauth2.clients[0].accessTokenValidateSeconds = 3600
imooc.security.oauth2.clients[1].clientId = test
imooc.security.oauth2.clients[1].clientSecret = test


十、使用JWT替换默认令牌


1、TokenStoreConfig中定义TokenStore,为了与redisTokenStore不冲突,要使用@ConditionalOnProperty注解进行处理
/**
* 使用jwt时的配置,默认生效
*
* @author zhailiang
*
*/
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.oauth2", name = "tokenStore", havingValue = "jwt", matchIfMissing = true)
public static class JwtConfig {

@Autowired
private SecurityProperties securityProperties;

/**
* jwt存储
* @return
*/
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}

/**
* jwttoken生成
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());//使用jwt时为token签名的秘钥
return converter;
}

/**
* jwt token额外信息添加,可以通过实现TokenEnhancer接口,添加额外信息
* @return
*/
@Bean
@ConditionalOnBean(TokenEnhancer.class)
public TokenEnhancer jwtTokenEnhancer(){
return new TokenJwtEnhancer();
}

}
2、ImoocAuthorizationServerConfig配置

@Autowired(required = false)
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Autowired(required = false)
private TokenEnhancer jwtTokenEnhancer;

/**
* 认证及token配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);

//以下是与jwttoken生成及额外信息添加设置
if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(jwtTokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancers);
endpoints.tokenEnhancer(enhancerChain).accessTokenConverter(jwtAccessTokenConverter);
}
}
3、jwtToken中包含用户信息,并且可以http://www.jsonwebtoken.io/上进行解析
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImNvbXBhbnkiOiJpbW9vYyIsImV4cCI6MTUxMjU3ODEwNiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiZjFlNzI1YjgtOTdmNC00YmUzLThmNjAtNmE2MzA4N2NmMmJmIiwiY2xpZW50X2lkIjoiaW1vb2MifQ.TH1upg0M1x2U9WlhKNaLqE4jmKf3ey1X2aji9vN_mig",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImYxZTcyNWI4LTk3ZjQtNGJlMy04ZjYwLTZhNjMwODdjZjJiZiIsImNvbXBhbnkiOiJpbW9vYyIsImV4cCI6MTUxNTE2NTgzNywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiOWJmNDVmNGItZDhjNi00ZWNmLTg0M2MtZTJiNzdmYTk4NzI3IiwiY2xpZW50X2lkIjoiaW1vb2MifQ.mNDpVpY3DfV_6sJrxPfcsdEuCo65PYkH66zXdA0xrJ4",
"expires_in": 3599,
"scope": "all",
"company": "imooc",
"jti": "f1e725b8-97f4-4be3-8f60-6a63087cf2bf"
}
4、额外添加的信息,Authentication中并不会包含,所以为自己解析token获取
4.1、引用jar
<!--解析jwtToken-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
4.2、使用
@GetMapping("/me")
public Object getCurrentUser(Authentication user, HttpServletRequest request) throws UnsupportedEncodingException {

String token = StringUtils.substringAfter(request.getHeader("Authorization"), "bearer ");

//io.jsonwebtoken.Jwts默认是使用ios。。,所以要指定编码为utf-8
Claims claims = Jwts.parser().setSigningKey(securityProperties.getOauth2().getJwtSigningKey().getBytes("UTF-8"))
.parseClaimsJws(token).getBody();

String company = (String) claims.get("company");

System.out.println(company);

return user;
}


十一、刷新令牌post


url:http://{{ip:port}}/oauth/token
headers:
Content-Type:application/x-www-form-urlencoded
Authorization:Basic aW1vb2M6aW1vb2NzZWNyZXQ=
body:
grant_type:refresh_token
refresh_token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6IjRjYmVhNGYzLTkxZWUtNDk0Ny1hYzk4LTVkNTc1ZDAxNWRhYyIsImNvbXBhbnkiOiJpbW9vYyIsImV4cCI6MTUxNTE2NTgzNywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiOWJmNDVmNGItZDhjNi00ZWNmLTg0M2MtZTJiNzdmYTk4NzI3IiwiY2xpZW50X2lkIjoiaW1vb2MifQ.s5DEvHpu4BOKJnkr2xSgCIGarZmynO6AiQTdEj8Q9wQ
scope:all
return:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImNvbXBhbnkiOiJpbW9vYyIsImV4cCI6MTUxMjU3ODEwNiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiZjFlNzI1YjgtOTdmNC00YmUzLThmNjAtNmE2MzA4N2NmMmJmIiwiY2xpZW50X2lkIjoiaW1vb2MifQ.TH1upg0M1x2U9WlhKNaLqE4jmKf3ey1X2aji9vN_mig",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImYxZTcyNWI4LTk3ZjQtNGJlMy04ZjYwLTZhNjMwODdjZjJiZiIsImNvbXBhbnkiOiJpbW9vYyIsImV4cCI6MTUxNTE2NTgzNywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiOWJmNDVmNGItZDhjNi00ZWNmLTg0M2MtZTJiNzdmYTk4NzI3IiwiY2xpZW50X2lkIjoiaW1vb2MifQ.mNDpVpY3DfV_6sJrxPfcsdEuCo65PYkH66zXdA0xrJ4",
"expires_in": 3599,
"scope": "all",
"company": "imooc",
"jti": "f1e725b8-97f4-4be3-8f60-6a63087cf2bf"
}


十二、基于JWT实现SSO单点登录


1、认证服务器
1.1、继承AuthorizationServerConfigurerAdapter进行客户端,token生成,安全配置
@Configuration
@EnableAuthorizationServer
public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("imooc1")
.secret("imoocsecrect1")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all")
.and()
.withClient("imooc2")
.secret("imoocsecrect2")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("all");
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("isAuthenticated()");
}

@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAc
cessTokenConverter());
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("imooc");
return converter;
}
}
1.2、使用表单认证替base认证
-SsoUserDetailsService
@Component
public class SsoUserDetailsService implements UserDetailsService {

@Autowired
private PasswordEncoder passwordEncoder;

/* (non-Javadoc)
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, passwordEncoder.encode("123456"),
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
-SsoSecurityConfig
@Configuration
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().and().authorizeRequests().anyRequest().authenticated();
}

}
1.3、spring security OAuth2默认在认证成功后跳到授权页面,要用户点击授权才可以访问资源
这个页面由@FrameworkEndpoint WhitelabelApprovalEndpoint中定义,可以通过自定义一个WhitelabelApprovalEndpoint并使用@RestController可以覆盖原来的controller
需要新增相关的类有:SsoApprovalEndpoint(复制于WhitelabelApprovalEndpoint,修改template添加<script>document.getElementById('confirmationForm').submit()</script>
SsoSpelView
1.4、Application.properties中
server.port = 9999
server.context-path = /server

2、应用1(应用2与应用1相似,将1更改为2)
2.1、启动main方法添加@EnableOAuth2Sso注解
@SpringBootApplication
@EnableOAuth2Sso
@RestController
public class SsoClient1Application {

@GetMapping("/user")
public Authentication user(Authentication user) {
return user;
}

public static void main(String[] args) {
SpringApplication.run(SsoClient1Application.class, args);
}
}
2.2、Application.properties中
security.oauth2.client.clientId = imooc1
security.oauth2.client.clientSecret = imoocsecrect1
security.oauth2.client.user-authorization-uri = http://127.0.0.1:9999/server/oauth/authorize
security.oauth2.client.access-token-uri = http://127.0.0.1:9999/server/oauth/token
security.oauth2.resource.jwt.key-uri = http://127.0.0.1:9999/server/oauth/token_key

server.port = 8080
server.context-path = /client1

2.3、建一个index.html,用于跳转到client2应用中
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SSO Client2</title>
</head>
<body>
<h1>SSO Demo Client2</h1>
<a href="http://127.0.0.1:8080/client1/index.html">访问Client1</a>
</body>
</html>