20190111
1.学到的知识
1.1.记录问题
1.1.1回到昨天的问题,继续解决:
- 报错二:
postman的返回结果信息如下:
将日志输出转换为debug,查看控制台日志如下:
2019-01-11 00:13:04.663 DEBUG 21056 --- [http-nio-9090-exec-6] o.s.boot.actuate.audit.listener.AuditListener : AuditEvent [timestamp=2019-01-10T16:13:04.663Z, principal=access-token, type=AUTHENTICATION_FAILURE, data={type=org.springframework.security.authentication.BadCredentialsException, message=Cannot convert access token to JSON}]
2019-01-11 00:13:04.666 DEBUG 21056 --- [http-nio-9090-exec-6] o.s.security.web.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@64132b53
2019-01-11 00:13:04.668 DEBUG 21056 --- [http-nio-9090-exec-6] o.s.s.o.p.error.DefaultOAuth2ExceptionRenderer : Written [error="invalid_token", error_description="Cannot convert access token to JSON"] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@eb68d2f]
2019-01-11 00:13:04.669 DEBUG 21056 --- [http-nio-9090-exec-6] o.s.s.web.context.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
针对报错信息,自己先回头看了下自己写的逻辑代码,从源头出发,发现是这个 authorization 的前缀出现了问题,也就是请求头应该是 Basic,而不是Bearer,前者是用来 做服务端认证的标示,后者是权限信息判断的标示。因为标示不对,所以一直提示转换问题。
对请求头的参数进行了打印:
{request=org.springframework.cloud.netflix.zuul.filters.pre.Servlet30RequestWrapper@77410b21, response=com.netflix.zuul.http.HttpServletResponseWrapper@50e9453a, zuulRequestHeaders={authorization=Bearer dXNlci1zZXJ2aWNlOjEyMzQ1Ng==, content-type=application/x-www-form-urlencoded;charset=UTF-8}, zuulEngineRan=true, executedFilters=ServletDetectionFilter[SUCCESS][0ms], Servlet30WrapperFilter[SUCCESS][0ms], FormBodyWrapperFilter[SUCCESS][7ms], isDispatcherServletRequest=true}
对于登录接口过滤的时候,原写法如下(错误)
ctx.addZuulRequestHeader("Authorization", "Bearer " + accessToken);
正确的写法应该是:
ctx.addZuulRequestHeader("Authorization", accessToken);
1.1.2新问题:无权访问:
错误日志如下:
一直提示无权访问
2019-01-11 20:27:10.170 DEBUG 16776 --- [http-nio-9090-exec-2] o.s.s.w.access.intercept.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@f70a0382: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2019-01-11 20:27:10.170 DEBUG 16776 --- [http-nio-9090-exec-2] o.s.security.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@305050b0, returned: -1
2019-01-11 20:27:10.170 DEBUG 16776 --- [http-nio-9090-exec-2] o.s.boot.actuate.audit.listener.AuditListener : AuditEvent [timestamp=2019-01-11T12:27:10.170Z, principal=anonymousUser, type=AUTHORIZATION_FAILURE, data={details=org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null, type=org.springframework.security.access.AccessDeniedException, message=Access is denied}]
2019-01-11 20:27:10.171 DEBUG 16776 --- [http-nio-9090-exec-2] o.s.security.web.access.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
- 关键信息:
Access is denied (user is anonymous); redirecting to authentication entry point
解决办法一:
在网上找到一些相关的资料,说是过滤器没有被调用。
帖子:
https://stackoverflow.com/questions/44171633/spring-boot-oauth2-access-is-denied-user-is-anonymous-redirecting-to-authen?rq=1
然后自己再控制台找,发现是被调用了的:
已经被调用了
- 结果:排除这个问题。
解决办法二:
作者的遇到的问题应该是 resource-service 远程调用uaa-service的时候需要权限信息,这个是没错的,这里他就需要上面那个 basic 开头的参数,来对请求者进行判断,看是否符合条件。看到这一点,自己也加了 .antMatchers("/oauth/token").permitAll() ,加完之后,发现错误还是之前那个,那么这个时候,就排查了不是这个uaa的问题了。
在后面的时候,想了下,看日志可以知道还没执行到这一步呢。这一步当时有点多余(脑子有点糊涂了)。
- 结果:无效,不是这个问题。
解决办法三:
找到的资料:
https://blog.coding.net/blog/Explore-the-cache-request-of-Security-Spring
说到得是 Spring Security 缓存请求 这个点了,大意就是第一次请求的时候,认证的信息存放在header中,第一次请求受保护资源时,请求头中不包含认证信息 ,验证失败,该请求会被缓存,之后即使用户填写了信息,也会因为request被恢复导致信息丢失从而认证失败,所以解决办法就是不缓存request。
默认情况下,三种request不会被缓存。
- 请求地址以/favicon.ico结尾
- header中的content-type值为application/json
- header中的X-Requested-With值为XMLHttpRequest
好,针对这一点,自己也将不缓存request,在HttpSecurity中设置下面:
http.requestCache().requestCache(new NullRequestCache());
- 结果:不理想,仍不能解决问题。
后面思考总结的时候,想了下,如果是缓存的问题,那么我关闭服务的时候,就已经清掉了缓存呀,这也是一个验证的方式,但是搞了一天,当时思路就很迷了。
解决办法四:
自己问了之前用过Zuul和springcloud的朋友,讨论了很久之后,将问题定位到 zuul路由这个点上了。朋友提点可能是我的url设置有问题。
也就是说,zuul路由的时候,设置了会为每一个 微服务附带一个url前缀,根据我的设置:请看代码一,结合着下面的设置,可以知道我是给我 sources-service微服务,增设了 /a/ 这个前缀。如此,自己将自己的接口重新更改过来,在原先的接口 /a/login 更改为 /login 。由此解决了这个搞了一天的问题。
-
分析
按照上面所说,zuul路由之前会为每一个微服务增加一个你自设得 url前缀,而此时如果你接口使用的是 /a/login,那么在路由之前的地址就是 /a/a/login,这样的因为不符合 filter条件也将被过滤掉,提示 接口不存在。所以就是在接口处,去掉 /a/ ,同时 HttpSecurity 中,增设开放不需要authorization的 url地址。也就是再下面代码二: -
代码一
zuul:
routes:
api-a:
path: /a/**
serviceId: sources-service
api-b:
path: /auth/**
serviceId: uaa-service
- 代码二
@Override
public void configure(HttpSecurity http) throws Exception {
http
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// 允许对于网站静态资源的无授权访问
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
// 对于获取token的rest api要允许匿名访问
.antMatchers("/auth/**").permitAll()
.antMatchers("/swagger-ui.html","/a/login","/a/register","/login").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 禁用缓存
http.headers().cacheControl();
}