org.springframework.security.access.AccessDeniedException: Access is denied
2021-04-10 16:17:01.950 DEBUG [authorization-server,8c5f48650de0f659,83580d93d6acb342,false] 7328 — [nio-8000-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : ‘/oauth/token’; against ‘/oauth/token’
2021-04-10 16:17:01.950 DEBUG [authorization-server,8c5f48650de0f659,83580d93d6acb342,false] 7328 — [nio-8000-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /oauth/token?scope=read&grant_type=password; Attributes: [fullyAuthenticated]
2021-04-10 16:17:01.951 DEBUG [authorization-server,8c5f48650de0f659,83580d93d6acb342,false] 7328 — [nio-8000-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@a50fab08: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@7798: RemoteIpAddress: 10.68.23.139; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2021-04-10 16:17:01.953 DEBUG [authorization-server,8c5f48650de0f659,83580d93d6acb342,false] 7328 — [nio-8000-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@1bbd303b, returned: -1
http.csrf().disable();
http.authorizeRequests()
// .anyRequest().permitAll();
.antMatchers("/**").permitAll()
// .antMatchers("/actuator/**").permitAll()
// .antMatchers("/oauth/**").permitAll()
// .anyRequest().authenticated()
.and()
.cors();
at org.springframework.security.access.vote.AffirmativeBased.decide
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
Iterator var5 = this.getDecisionVoters().iterator();
while(var5.hasNext()) {
AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
int result = voter.vote(authentication, object, configAttributes);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Voter: " + voter + ", returned: " + result);
}
switch(result) {
case -1:
++deny;
break;
case 1:
return;
}
}
if (deny > 0) {
throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
//就是这里抛出的异常
} else {
this.checkAllowIfAllAbstainDecisions();
}
}
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
int vote(Authentication var1, S var2, Collection<ConfigAttribute> var3);
}
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return -1;
} else {
int result = 0;
Collection<? extends GrantedAuthority> authorities = this.extractAuthorities(authentication);
Iterator var6 = attributes.iterator();
while(true) {
ConfigAttribute attribute;
//这是一个存储着SpringConfig配置对象类,内部有一个静态list列表存储设置对象
do {
if (!var6.hasNext()) {
return result;
//到达最后一个,返回0
}
attribute = (ConfigAttribute)var6.next();
//循环
} while(!this.supports(attribute));
result = -1;
Iterator var8 = authorities.iterator();
while(var8.hasNext()) {
GrantedAuthority authority = (GrantedAuthority)var8.next();
if (attribute.getAttribute().equals(authority.getAuthority())) {
return 1;
}
}
}
}
}
//判断角色是否以ROLE_开头,即从数据库/配置文件中读取的角色列表
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute() != null && attribute.getAttribute().startsWith(this.getRolePrefix());
}
这里的逻辑大概是每次从配置文件中读取一个符合格式的ROlE,即可以访问该URL的ROLE角色,然后从当前角色Authority列表中遍历,如果找到符合角色直接返回1;如果到达最后一个,仍未找到符合角色对象,那么返回-1,退出循环
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
private List<AccessDecisionVoter<? extends Object>> decisionVoters;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private boolean allowIfAllAbstainDecisions = false;
回到最前面的decide方法,我们可以知道AbstractAccessDecisionManager中维护一个decisionVoters列表,来判断当前访问的url是否有权限,不仅仅包括角色,同时还有其他的Voter实现类
我们进到判断权限的类
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {
return authentication.getAuthorities();
}
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
Authentication实现了Principal接口,存储着用户的一系列权限,还可以设置授权状态
看他的实现类
public abstract class AbstractAuthenticationToken{
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append(": ");
sb.append("Principal: ").append(this.getPrincipal()).append("; ");
sb.append("Credentials: [PROTECTED]; ");
sb.append("Authenticated: ").append(this.isAuthenticated()).append("; ");
sb.append("Details: ").append(this.getDetails()).append("; ");
if (!this.authorities.isEmpty()) {
sb.append("Granted Authorities: ");
int i = 0;
GrantedAuthority authority;
for(Iterator var3 = this.authorities.iterator(); var3.hasNext(); sb.append(authority)) {
authority = (GrantedAuthority)var3.next();
if (i++ > 0) {
sb.append(", ");
}
}
} else {
sb.append("Not granted any authorities");
}
return sb.toString();
}
}
直接看到toString方法!!Granted Authorities: ROLE_ANONYMOUS我们可以直接看到就是这里打出来的匿名用户消息,胜利就在前方!
看到这个抽象类的实现类,直接看到OAuthAuthentication
public class OAuth2Authentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = -4809832298438307309L;
private final OAuth2Request storedRequest;
private final Authentication userAuthentication;
public OAuth2Authentication(OAuth2Request storedRequest, Authentication userAuthentication) {
//构造函数,传入Oath2request和当前用户信息,Security把登陆请求的token存储在Oauth2Request中
super(userAuthentication == null ? storedRequest.getAuthorities() : userAuthentication.getAuthorities());
this.storedRequest = storedRequest;
this.userAuthentication = userAuthentication;
}
public Object getCredentials() {
return "";
}
public Object getPrincipal() {
return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication.getPrincipal();
}
public boolean isClientOnly() {
return this.userAuthentication == null;
}
public OAuth2Request getOAuth2Request() {
return this.storedRequest;
}
public Authentication getUserAuthentication() {
return this.userAuthentication;
}
public boolean isAuthenticated() {
return this.storedRequest.isApproved() && (this.userAuthentication == null || this.userAuthentication.isAuthenticated());
}
public void eraseCredentials() {
super.eraseCredentials();
if (this.userAuthentication != null && CredentialsContainer.class.isAssignableFrom(this.userAuthentication.getClass())) {
((CredentialsContainer)CredentialsContainer.class.cast(this.userAuthentication)).eraseCredentials();
}
}
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (!(o instanceof OAuth2Authentication)) {
//传入的是不是OAUTH2Authentication接口实现类。如果是,继续
return false;
} else if (!super.equals(o)) {
//调用当前父类equals方法,父类方法是判断两个token是否相等
return false;
} else {
OAuth2Authentication that = (OAuth2Authentication)o;
if (!this.storedRequest.equals(that.storedRequest)) {
return false;
} else {
label33: {
if (this.userAuthentication != null) {
if (this.userAuthentication.equals(that.userAuthentication)) {
break label33;
}
} else if (that.userAuthentication == null) {
break label33;
}
return false;
}
//先判断userAuthentication是否相等,在判断detail是否相等,如果都相等再返回true
if (this.getDetails() != null) {
if (!this.getDetails().equals(that.getDetails())) {
}
} else if (that.getDetails() != null) {
}
return true;
}
}
}
public int hashCode() {
int result = super.hashCode();
result = 31 * result + this.storedRequest.hashCode();
result = 31 * result + (this.userAuthentication != null ? this.userAuthentication.hashCode() : 0);
return result;
}
}
看完这里还是没有看见匿名用户在哪。。。先看看这个Authentication是从哪来的
在类 AbstractSecurityInterceptor中找到一段
Authentication authenticated = this.authenticateIfRequired();
private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.isAuthenticated() && !this.alwaysReauthenticate) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Previously Authenticated: " + authentication);
}
return authentication;
} else {
authentication = this.authenticationManager.authenticate(authentication);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
}
原来是从SpringContextHolder里来的,由spring管理的threadlocal存储的用户登陆状态,在用户发送请求时候全局进行存储的,通过用户发送的token解析出登陆状态交由spring管理,我这里之前打印出了 this.logger.debug("Previously Authenticated: " + authentication);
说明是已经通过token解析了我的登陆信息了,也就是说我带来了token,没有过期,但是我当前访问的url我没有权限,也就是/oauth/**没有权限,但是不带token是可以正常访问接口的。。。
service.interceptors.request.use(config => {
// if (!(typeof(store.getters.token)=='undefined')) {
// config.headers['Authorization'] = 'Bearer '+getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
// }
加上上面一段后,当有token时候不能访问登陆接口,我的需求是返回用户已经登陆了
return config
}, error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
})
未完待续。。。。