Spring Security - Angular JS - 使用自定义GenericFIlterBean进行基于令牌的身份验证

时间:2014-06-10 09:13:35

标签: java angularjs authentication spring-security

我遇到了使用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;。这肯定是可配置的,但也很难记录。 无论如何,我希望这些信息对其他人有用。

0 个答案:

没有答案