解决问题:
org.springframework.boot
spring-boot-starter-parent
1.5.16.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
org.springframework.security.oauth
spring-security-oauth2
/**
* 配置资源服务器
*/
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler) //配置登出自定义接口
.and()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/backstage/**").authenticated()
// .and().httpBasic() //添加该配置即可使用http basic验证方式(见4.2)
;
}
//配置自定义错误返回
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
;
}
}
/**
* 配置授权服务
*/
@Configuration
@EnableAuthorizationServer //实现EnvironmentAware接口可以重写其setEnvironment方法,用来读取配置文件的属性
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;//该类包含读取配置文件的方法
@Autowired
private DataSource dataSource;
@Autowired
private MyWebResponseExceptionTranslator myWebResponseExceptionTranslator;
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
//使用 JdbcTokenStore(或使用自定义TokenStore)
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
//token扩展
@Bean
@ConditionalOnMissingBean(name = "tokenEnhancer")
public TokenEnhancer tokenEnhancer() {
return new MyTokenEnhancer();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenEnhancer(tokenEnhancer()) //token扩展
.tokenStore(tokenStore()) //tokenStore
.authenticationManager(authenticationManager) //开启密码授权模式
.userDetailsService(myUserDetailsService) //对用户详细信息的检查,比如查看是否存在
.exceptionTranslator(myWebResponseExceptionTranslator)
;
}
// 可以用来定义一个基于内存的或者JDBC的客户端信息服务。
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory() //此处基于内存
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorizedGrantTypes("password") //密码模式
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800)); //优先读配置文件,没有则用默认值
}
//实现EnvironmentAware接口可以重写其setEnvironment方法,用来读取配置文件的属性;
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
}
############# oauth2 ######################
authentication.oauth.clientid=abc
authentication.oauth.secret=123456
authentication.oauth.tokenValidityInSeconds=18000
#将资源拦截的过滤器运行顺序放到第3个执行,也就是在oauth2的认证服务器后面执行
security.oauth2.resource.filter-order = 3
/**
* Description:开启SpringSecurity配置
* User: adg
* Date: 2019/2/25
*/
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
//自定义UserDetailsService注入
@Autowired
private MyUserDetailsService userDetailsService;
//配置匹配用户时密码规则
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//配置全局设置
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//设置UserDetailsService以及密码规则
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//排除路径拦截
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/logout");
}
//跨域
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers(HttpMethod.OPTIONS, "/oauth/**","/logout")
//支持跨域
.and()
.cors()
.and()
.csrf().disable();
}
//全局 CORS Filter
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
以下类都需在【1.2.配置资源服务】中配置
//Description:自定义401错误码,请求未带token或token失效
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {……}
//Description:自定义400禁止访问
@Slf4j
@Component("myAccessDeniedHandler")
public class MyAccessDeniedHandler implements AccessDeniedHandler {……}
以下类都需在【1.3.配置权限服务】中配置
//自定义异常解释器,可根据异常自定义返回信息,如账号密码错误
@Component
public class MyWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
@Override
public ResponseEntity translate(Exception e) throws Exception {……}
}
//自定义UserDetailsService
@Component("myDetailsService")
public class MyUserDetailsService implements UserDetailsService {……}
public class MyTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
if (accessToken instanceof DefaultOAuth2AccessToken) {
DefaultOAuth2AccessToken token = ((DefaultOAuth2AccessToken) accessToken);
// token.setValue(getNewToken());
// OAuth2RefreshToken refreshToken = token.getRefreshToken();
// if (refreshToken instanceof DefaultOAuth2RefreshToken) {
// token.setRefreshToken(new DefaultOAuth2RefreshToken(getNewToken()));
// }
Map mtoken = new HashMap<>();
mtoken.put("token",accessToken.getValue());
Map map = new HashMap();
map.put("code", 0);
map.put("resultMsg", "操作成功");
map.put("data", mtoken);
token.setAdditionalInformation(map);//附加信息
}
return accessToken;
}
private String getNewToken() {
return "123" + UUID.randomUUID().toString().replace("-", "");
}
}
获取token
登出
获取token时前端请求头加如下(标红为base64的 (格式—> clientId:password ,是clientId和其密码,不是用户名和密码)
Authorization: 'Basic 此处为base64的clientId:password'
访问接口时前端请求头加如下
Authorization: 'Bearer 5f476c06-bfee-4121-b8b4-47aa598bde5a'
Http基本认证【http baisc】
Authorization: 'Basic 此处为base64的用户名密码'
spring-security的认证为一系列过滤器链。我们只需定义一个比OAuth2AuthenticationProcessingFilter更早的过滤器拦截指定请求,去除header中的Authorization Bearer xxxx即可。
@Component("permitAuthenticationFilter")
@Slf4j
public class PermitAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("当前访问的地址:{}", request.getRequestURI());
String requestURI = request.getRequestURI();
List list = new ArrayList<>();
list.add("/backstage/picture/search/list");
list.add("/backstage/picture/searchBase64/list");
list.add("/backstage/event/realTime/list");
list.add("/api/event/realTime/list");
if (list.contains(requestURI)) {
request = new HttpServletRequestWrapper(request) {
private Set headerNameSet;
@Override
public Enumeration getHeaderNames() {
if (headerNameSet == null) {
// first time this method is called, cache the wrapped request's header names:
headerNameSet = new HashSet<>();
Enumeration wrappedHeaderNames = super.getHeaderNames();
while (wrappedHeaderNames.hasMoreElements()) {
String headerName = wrappedHeaderNames.nextElement();
if (!"Authorization".equalsIgnoreCase(headerName)) {
headerNameSet.add(headerName);
}
}
}
return Collections.enumeration(headerNameSet);
}
@Override
public Enumeration getHeaders(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return Collections.emptyEnumeration();
}
return super.getHeaders(name);
}
@Override
public String getHeader(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return null;
}
return super.getHeader(name);
}
};
}
filterChain.doFilter(request, response);
}
}
/**
* 配置过滤器
*/
@Component("permitAllSecurityConfig")
public class PermitAllSecurityConfig extends SecurityConfigurerAdapter {
@Autowired
private Filter permitAuthenticationFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(permitAuthenticationFilter, OAuth2AuthenticationProcessingFilter.class);
}
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
……
.and()
.apply(permitAllSecurityConfig)
……
.authorizeRequests()
.antMatchers("permit/login").permitAll()
……
oauth的设计是用户不向第三方暴露自己的登陆信息的情况下,授权第三方以自己的身份访问一些资源。
oauth2的设计初衷是源于oauth1计算签名的复杂度,希望进一步降低第三方开发者的开发门槛。