1.oauth2
oauth2(开放授权) 是一个开放标准,在不需要用户将账号密码提供给第三方应用,来授权访问,比如微信的第三方登陆
oauth2 有四种模式:
授权码模式(authorization_code ),
密码模式(password), 用户名和密码访问
隐式授权模式(Implicit Grant),
客户端凭证模式(Client Credentials Grant) 服务器通信的场景
2.授权码模式:
授权码模式是功能最完整,流程最严密的授权模式。
1. 用户先访问客户端,客户端去认证服务器,认证服务器通过,返回一个授权码 并且重定向到指定的URL中,
例如: http://localhost:8080/oauth2/oauth/authorize?grant_type=authorization_code&client_id=console&client_secret=console&redirect_uri=http://127.0.0.1:8080/token/getToken&response_type=code
3. 客户端拿到授权授权码,以及之前的重定向URL,去认证服务器申请token。
授权服务器配置:
package com.homeinns.config.autoconfigure;
import com.homeinns.service.JdbcUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@EnableAuthorizationServer
@Conditional(value = EnableOauth2.class) //是否开启oauth2认证
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenConfigure tokenConfigure;
@Autowired
@Qualifier("jdbcUserDetailsService")
private JdbcUserDetailsService userDetailsService;
@Autowired
@Qualifier("clientAuthenticationManager")
private AuthenticationManager authenticationManager;
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
@Qualifier("jwtAccessTokenConverter")
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenEnhancer jwtTokenEnhancer;
@Autowired
private WebResponseExceptionTranslator customWebResponseExceptionTranslator;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore) //指定token存储位置
.authenticationManager(authenticationManager) //指定认证管理器
.userDetailsService(userDetailsService); //
// 自定义token生成方式
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancerList = new ArrayList();
enhancerList.add(jwtTokenEnhancer);
enhancerList.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(enhancerList);
endpoints.tokenEnhancer(tokenEnhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
endpoints.exceptionTranslator(customWebResponseExceptionTranslator);
// 配置TokenServices参数
DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// token有效期自定义设置,默认12小时
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
//刷新token的有效期,默认一天
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24);
endpoints.tokenServices(tokenServices);
}
//客户端配置,目前存储在内存中,可以改为数据库存储
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
ClientDetailsServiceBuilder<org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder>.ClientBuilder scopes = clients.inMemory().withClient(tokenConfigure.getClientId())
.secret(tokenConfigure.getOauth2Secret())
.redirectUris(tokenConfigure.getRedirectUri())
.refreshTokenValiditySeconds(tokenConfigure.getRefreshToken().intValue())
.accessTokenValiditySeconds(tokenConfigure.getAccessToken().intValue())
.scopes(tokenConfigure.getScope());
//授权模式
for (String grantType : tokenConfigure.getGrantTypes()) {
scopes.authorizedGrantTypes(grantType);
}
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()"); // 开启/oauth/token_key验证端口无权限访问
security.checkTokenAccess("isAuthenticated()"); // 开启/oauth/check_token验证端口认证权限访问
security.allowFormAuthenticationForClients();
}
}
资源服务器配置:
package com.homeinns.config.autoconfigure;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.homeinns.config.token.WGUTTokenExtractor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 自定义资源服务器配置(校验token)
* <p>
*/
@Component
@Conditional(value = EnableOauth2.class)
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private TokenConfigure tokenConfigure;
@Autowired
@Qualifier("clientAuthenticationManager")
private AuthenticationManager authenticationManager;
@Autowired
private DefaultTokenServices defaultTokenServices;
@Override
public void configure(ResourceServerSecurityConfigurer config) {
config.resourceId(tokenConfigure.getResourceId()).stateless(true);
config.tokenExtractor(new WGUTTokenExtractor());
config.authenticationManager(authenticationManager);
config.tokenServices(defaultTokenServices);
config.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
Throwable cause = e.getCause();
Map map = new HashMap<>();
if (cause instanceof InvalidTokenException) {
map.put("code", "401");//401
map.put("msg", "无效的token");
} else {
map.put("code", "401");//401
map.put("msg", e.getMessage());
}
map.put("data", e.getMessage());
map.put("success", false);
map.put("path", request.getServletPath());
map.put("timestamp", String.valueOf(new Date().getTime()));
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), map);
} catch (Exception e1) {
throw new ServletException(e1);
}
}
});
config.accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
@Override
public void configure(HttpSecurity http) throws Exception {
List<String> resourceUrls = tokenConfigure.getResourceUrls();
String[] strings = new String[resourceUrls.size()];
resourceUrls.toArray(strings);
http.headers().frameOptions().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
http.requestMatchers().antMatchers(strings) //哪些资源需要保护
.and().authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
// .and().requiresChannel().antMatchers("/loginTest", "/oauth/**").requiresSecure();
super.configure(http);
}
}
安全服务器配置
package com.homeinns.config.autoconfigure;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
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.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.stereotype.Component;
@Component
@EnableWebMvcSecurity
public class WebMvcAdapter extends WebSecurityConfigurerAdapter implements InitializingBean {
@Autowired
@Qualifier("clientAuthenticationManager")
private AuthenticationManager authenticationManager;
/**
* 配置这个bean会在做AuthorizationServerConfigurer配置的时候使用
*
* @return
* @throws Exception
*/
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager;
}
@Autowired
@Qualifier("daoAuthenticationProvider")
private AuthenticationProvider authenticationProvider;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**", "resource/**","/**/*.html");
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/**").permitAll()
.and().requiresChannel().antMatchers("/**/token/**", "/**/oauth/**").requiresSecure() // https
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler())
.and().csrf().disable();// 取消跨站请求伪造防护
super.configure(http);
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Override
public void afterPropertiesSet() throws Exception {
System.err.println("---------------ENABLE WEBMVC-----------------");
}
}
配置类:
package com.homeinns.config.autoconfigure;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Configuration
@Conditional(value = EnableOauth2.class)
public class TokenConfigure implements Serializable {
private Long accessToken = 60 * 60L;
private Long refreshToken = 60 * 60 * 2L;
private String secret;
private String resourceId = "console";
private String clientId = "console";
private String oauth2Secret = "console";
private String scope = "all";
private String redirectUri = "http://127.0.0.1:8080/token/getToken";
private List<String> grantTypes = getDefaultGrantTypes();
//资源认证的路径
private List<String> resourceUrls = getDefaultResourceUrl();
public Long getAccessToken() {
return accessToken;
}
public void setAccessToken(Long accessToken) {
this.accessToken = accessToken;
}
public Long getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(Long refreshToken) {
this.refreshToken = refreshToken;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public List<String> getResourceUrls() {
return resourceUrls;
}
public void setResourceUrls(List<String> resourceUrls) {
this.resourceUrls = resourceUrls;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getOauth2Secret() {
return oauth2Secret;
}
public void setOauth2Secret(String oauth2Secret) {
this.oauth2Secret = oauth2Secret;
}
public List<String> getGrantTypes() {
return grantTypes;
}
public void setGrantTypes(List<String> grantTypes) {
this.grantTypes = grantTypes;
}
private static List<String> getDefaultGrantTypes() {
ArrayList<String> grantTypes = new ArrayList<>();
grantTypes.add("authorization_code");
grantTypes.add("password");
grantTypes.add("refresh_token");
return grantTypes;
}
private static List<String> getDefaultResourceUrl() {
ArrayList<String> urls = new ArrayList<>();
urls.add("/api/**");
return urls;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public String getResourceId() {
return resourceId;
}
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
}
controller:
package com.homeinns.web;
import com.google.common.collect.Maps;
import com.homeinns.config.autoconfigure.EnableOauth2;
import com.homeinns.config.autoconfigure.TokenConfigure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Conditional;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
@org.springframework.stereotype.Controller
@RequestMapping("/token")
@Conditional(value = EnableOauth2.class)
public class Controller {
private RestTemplate restTemplate = new RestTemplate();
@Autowired
TokenEndpoint tokenEndpoint;
@Autowired
private TokenConfigure tokenConfigure;
public MultiValueMap<String, String> test() {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", tokenConfigure.getClientId());
params.add("client_secret", tokenConfigure.getOauth2Secret());
params.add("redirect_uri", tokenConfigure.getRedirectUri());
return params;
}
@RequestMapping("/getToken")
@ResponseBody
public String test(@RequestParam String code) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = test();
params.add("code", code);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:8080/oauth/token", requestEntity, String.class);
String token = response.getBody();
return token;
}
@RequestMapping("/getTokenByPassword")
@ResponseBody
public ResponseEntity getTokenByPassword(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpServletRequest request) throws HttpRequestMethodNotSupportedException {
MultiValueMap<String, String> test = test();
test.remove("grant_type");
test.add("grant_type", "password");
test.add("username", username);
test.add("password", password);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("console", "console",null);
HashMap<String,String> stringListHashMap = Maps.newHashMap();
stringListHashMap.put("password",password);
stringListHashMap.put("username",username);
stringListHashMap.put("grant_type","password");
stringListHashMap.put("client_id","console");
stringListHashMap.put("client_secret","console");
authRequest.setDetails(stringListHashMap);
ResponseEntity<OAuth2AccessToken> oAuth2AccessTokenResponseEntity = tokenEndpoint.postAccessToken(authRequest,stringListHashMap);
// ResponseEntity<String> response = restTemplate.postForEntity("https://localhost:8080/oauth/oauth/token", requestEntity, String.class);
return oAuth2AccessTokenResponseEntity;
}
}
自定义校验
package com.homeinns.config.token;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* 自定义校验参数获取 可以判断参数也可以判断Authorization
*/
public class WGUTTokenExtractor extends BearerTokenExtractor {
@Override
protected String extractHeaderToken(HttpServletRequest request) {
String wgut = request.getParameter("WGUT");
if (StringUtils.isEmpty(wgut)) {
return super.extractHeaderToken(request);
}
return wgut;
}
}
token加密:
package com.homeinns.config.token;
import com.homeinns.service.TMUser;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
/**
* jwt 可以用jwt解密获取。(可以对)对生成的token进行一次加密
* 生成token。 可以在这里token进行一次加密()
*/
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
OAuth2Request oAuth2Request = authentication.getOAuth2Request();
String grantType = oAuth2Request.getGrantType();
Object principal = authentication.getUserAuthentication().getPrincipal();
additionalInfo.put("login", System.currentTimeMillis());
switch (grantType) {
case "authorization_code":
if (principal instanceof TMUser) {
TMUser user = (TMUser) principal;
additionalInfo.put("username", user.getUsername());
break;
}
case "password":
default:
additionalInfo.put("username", principal);
break;
}
additionalInfo.put("customExp",accessToken.getExpiresIn());
String value = accessToken.getValue();//对token加密一次
if (accessToken instanceof DefaultOAuth2AccessToken) {
DefaultOAuth2AccessToken defaultOAuth2AccessToken = (DefaultOAuth2AccessToken) accessToken;
defaultOAuth2AccessToken.setAdditionalInformation(additionalInfo);
defaultOAuth2AccessToken.setValue(value);
return defaultOAuth2AccessToken;
}
DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);
defaultOAuth2AccessToken.setAdditionalInformation(additionalInfo);
defaultOAuth2AccessToken.setValue(value);
return defaultOAuth2AccessToken;
}
}
token 校验
package com.homeinns.config.token;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.homeinns.config.autoconfigure.EnableOauth2;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
/**
* 解码 token
*/
@Configuration
@Conditional(value = EnableOauth2.class)
public class PreDecodeTokenProvider implements AuthenticationProvider {
//校验token
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof PreAuthenticatedAuthenticationToken) {
Object details = authentication.getDetails();
OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) details;
String tokenValue = oAuth2AuthenticationDetails.getTokenValue();
Jwt decode = JwtHelper.decode(tokenValue);
JSONObject jsonObject = JSON.parseObject(decode.getClaims());
Long exp = jsonObject.getLong("customExp");
Long login = jsonObject.getLong("login");
System.err.println((System.currentTimeMillis() - login) / 1000);
System.err.println();
return authentication;
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
LinkedHashMap details = (LinkedHashMap) authentication.getDetails();
if (details != null) {
Object grant_type = details.get("grant_type");
if ("password".equals(grant_type)) {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
} else if ("authorization_code".equals(grant_type)) {
String token = (String) authentication.getPrincipal();
return new UsernamePasswordAuthenticationToken(token, authentication.getCredentials(), new ArrayList<>());
}
}
}
return null;
}
@Override
public boolean supports(Class<?> authentication) {
// 这里直接改成retrun true;表示是支持这个执行
return true;
}
}
密码模式和授权码模式配置差不多。只是访问路径不同而已,
http://localhost:8080/oauth/token?username=user&password=user&grant_type=password&client_id=console&client_secret=console
来源:https://blog.csdn.net/qqBo1230/article/details/99671527