我遇到了使用spring安全框架进行基于令牌的身份验证的问题。 我想要实现的是使用基于spring的web服务来验证角度js客户端。 当客户端登录时,我生成一个令牌,该令牌与向后端发出的每个请求一起传递。到目前为止,这种方法运作良好。 生成令牌并成功返回给客户端。为每个请求传递令牌也是有效的。我的问题是我的自定义过滤器正在成功验证客户端,但在经过过滤器链的其余部分后,它总是返回一个HTTP 403,我不知道它来自哪里。
代码的灵感来自https://github.com/philipsorst/angular-rest-springsecurity,我尝试将其集成到我自己的应用中。
所以这是我的设置:
Java Spring Config Application.java:
@Configuration
@ComponentScan
@EnableAutoConfiguration
@ImportResource("classpath:applicationContext.xml")
@Order(1)
public class Application {
static Logger log = Logger.getLogger(Application.class.getName());
private static ConfigurableApplicationContext context;
public static void main(String[] args) {
context = SpringApplication.run(Application.class);
DBUtil dbUtil = context.getBean(DBUtil.class);
dbUtil.insertDestData();
}
}
@EnableWebMvcSecurity
@EnableWebSecurity(debug = true)
@ComponentScan
@Configuration
@EnableAutoConfiguration
@Order(2)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ConfigurableApplicationContext context;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(
SessionCreationPolicy.STATELESS);
String[] restEndpointsToSecure = { "api/recipe", "api/ingredient" };
//for (String endpoint : restEndpointsToSecure) {
http.authorizeRequests().antMatchers("/api/ingredient/create/")
.hasRole("USER");
//}
SecurityConfigurer<DefaultSecurityFilterChain, HttpSecurity> securityConfigurerAdapter = new XAuthTokenConfigurer(
userDetailsServiceBean(), authenticationManagerBean());
http.apply(securityConfigurerAdapter);
}
@Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder)
throws Exception {
UserDAO userDAO = context.getBean(UserDAO.class);
authManagerBuilder.userDetailsService(userDAO);
}
@Bean(name = "myAuthManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
实现UserDetail接口的用户实体
@Entity
@Table(name = "RB_USER")
public class User extends BaseAuditEntity implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 3719799602561353931L;
public User() {
super();
}
@Column(name = "NAME")
private String userName;
@Column(name = "PASSWORD")
private String password;
@Column(name = "SECTOKEN")
private String secToken;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<UserRole> userRoles;
@Column(name = "IS_ACCOUNT_NON_LOCKED")
private boolean isAccountNonLocked;
@Column(name = "IS_ENABLED")
private boolean isEnabled;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<String> roles = new HashSet<String>();
for (UserRole userRole : getUserRoles()) {
roles.add(userRole.getRole().getName());
}
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return userName;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return isAccountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return isEnabled;
}
public String getSecToken() {
return secToken;
}
public void setSecToken(String secToken) {
this.secToken = secToken;
}
public List<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRole(List<UserRole> userRoles) {
this.userRoles = userRoles;
}
public void setUserName(String name) {
this.userName = name;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(boolean isEnabled) {
this.isEnabled = isEnabled;
}
public void setAccountNonLocked(boolean isAccountNonLocked) {
this.isAccountNonLocked = isAccountNonLocked;
}
}
实现用户详细信息服务接口的用户dao
@Repository
@Transactional
public class UserDAOImpl extends GenericDAOImpl<User> implements UserDAO,
UserDetailsService {
public UserDAOImpl() {
super();
setClazz(User.class);
}
@Override
public User findByUserName(String userName) {
String queryString = "SELECT user FROM User user "
+ "LEFT JOIN FETCH user.userRoles userRoles "
+ "WHERE user.userName like ?1";
Query query = createQuery(queryString);
query.setParameter(1, userName);
return (User) query.getSingleResult();
}
@Override
public UserDetails loadUserByUsername(String username) {
String queryString = "SELECT user FROM User user "
+ "LEFT JOIN FETCH user.userRoles userRoles "
+ "WHERE user.userName like ?1";
Query query = createQuery(queryString);
query.setParameter(1, username);
return (User) query.getSingleResult();
}
}
我的扩展SecurityConfigurerAdapter将自定义过滤器应用于过滤器链
public class XAuthTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private UserDetailsService detailsService;
private AuthenticationManager authenticationManager;
public XAuthTokenConfigurer(UserDetailsService detailsService, AuthenticationManager authenticationManager) {
this.detailsService = detailsService;
this.authenticationManager = authenticationManager;
}
@Override
public void configure(HttpSecurity http) throws Exception {
XAuthTokenFilter customFilter = new XAuthTokenFilter(detailsService, authenticationManager);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
什么是过滤器本身的重要部分
public class XAuthTokenFilter extends GenericFilterBean {
protected Logger log = Logger.getLogger(this.getClass().getName());
private final UserDetailsService detailsService;
private final TokenUtils tokenUtils = new TokenUtils();
private final AuthenticationManager authenticationManager;
private String xAuthTokenHeaderName = "x-auth-token";
public XAuthTokenFilter(UserDetailsService userDetailsService, AuthenticationManager authenticationManager) {
this.detailsService = userDetailsService;
this.authenticationManager = authenticationManager;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String authToken = httpServletRequest
.getHeader(this.xAuthTokenHeaderName);
if (StringUtils.hasText(authToken)) {
String username = this.tokenUtils
.getUserNameFromToken(authToken);
UserDetails details = this.detailsService
.loadUserByUsername(username);
if (this.tokenUtils.validateToken(authToken, details)) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
details.getUsername(), details.getPassword(),
details.getAuthorities());
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
Authentication authentication = this.authenticationManager
.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("========================> " + SecurityContextHolder.getContext().getAuthentication().getName() + " , " + SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
}
}
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
正如我上面提到的,当后端返回一个令牌并且角度js app将令牌应用于每个请求时,登录工作正常。
现在,当我试图添加一种新的成分时,我目前唯一的终端是安全的(出于测试目的),我总是得到403 HTTP错误。
请求
Accept application/json, text/plain, */*
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Content-Length 15
Content-Type application/json;charset=utf-8
Cookie user=%7B%22id%22%3A1%2C%22version%22%3A0%2C%22createdAt%22%3A1402390667294%2C%22createdBy%22%3Anull%2C%22updatedAt%22%3Anull%2C%22updatedBy%22%3Anull%2C%22password%22%3A%22123%22%2C%22secToken%22%3A%22Vincent%3A1402394417353%3A62c641b65418ceecb47aad47d5fc5378%22%2C%22userRoles%22%3A%5B%7B%22id%22%3A1%2C%22version%22%3A0%2C%22createdAt%22%3A1402390667299%2C%22createdBy%22%3Anull%2C%22updatedAt%22%3Anull%2C%22updatedBy%22%3Anull%2C%22role%22%3A%7B%22id%22%3A1%2C%22version%22%3A0%2C%22createdAt%22%3A1402390667287%2C%22createdBy%22%3Anull%2C%22updatedAt%22%3Anull%2C%22updatedBy%22%3Anull%2C%22name%22%3A%22USER%22%7D%7D%5D%2C%22authorities%22%3A%5B%7B%22authority%22%3A%22USER%22%7D%5D%2C%22accountNonLocked%22%3Atrue%2C%22accountNonExpired%22%3Atrue%2C%22credentialsNonExpired%22%3Atrue%2C%22enabled%22%3Atrue%2C%22username%22%3A%22Vincent%22%7D
Host localhost:8080
Referer http://localhost:8080/
User-Agent Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0
x-auth-token Vincent:1402394417353:62c641b65418ceecb47aad47d5fc5378
正如您所见,令牌按预期传递,日志输出表明用户Vincent已成功通过身份验证
2014-06-10 11:00:56.201 INFO 4495 --- [nio-8080-exec-4] c.receiptbook.security.XAuthTokenFilter : ========================> Vincent , true
现在当过滤器链被处理时,它最终返回403错误,说明无法访问资源
POST /api/ingredient/create/
403 Forbidden
localhost:8080
所以我真的很感激任何提示或提示来解决这个问题,因为我真的没有想法如何追查这个问题。
此致 文森特
更新:
调试过滤器链时,我发现UsernamePasswordAuthenticationFilter甚至不在链中......? ExceptionTranslationFilter似乎对403 HTTP错误做出反应,但是从过滤器链中我无法说出他为什么会这样做出反应。
[0] WebAsyncManagerIntegrationFilter (id=75)
[1] SecurityContextPersistenceFilter (id=74)
[2] HeaderWriterFilter (id=71)
[3] LogoutFilter (id=83)
[4] XAuthTokenFilter (id=70)
[5] RequestCacheAwareFilter (id=116)
[6] SecurityContextHolderAwareRequestFilter (id=127)
[7] AnonymousAuthenticationFilter (id=130)
[8] SessionManagementFilter (id=133)
[9] ExceptionTranslationFilter (id=134)
[10] FilterSecurityInterceptor (id=138)
解决: 所以最后,经过一些我们的调试后,我终于解决了这个问题。唯一的问题是我的角色命名。我的角色我们刚刚命名为&#34; USER&#34;或&#34; ADMIN&#34;,但Spring安全性要求名称具有格式&#34; ROLE_USER&#34;或&#34; ROLE_ADMIN&#34;。这肯定是可配置的,但也很难记录。 无论如何,我希望这些信息对其他人有用。