Liferay中REST服务的身份验证和授权

4
我们正在开发一些通过RESTful API公开的服务。这个API的主要客户是使用AngularJS的Liferay门户,这意味着有来自客户端(AngularJS)到我们服务的直接调用。
到目前为止,我们已经设计了一个身份验证和授权机制,以确保我们可以识别请求我们API的已登录用户(Liferay)。
需要注意的是,尽管我们正在使用Liferay,但它也可以是任何其他基于Java的应用程序。
我们设计的内容是:
1. 当用户在我们门户中登录时,Liferay创建一个身份验证令牌,其中包含userLogin(或ID)+ client IP + timestamp。该令牌保存在cookie中;
2. 在每次REST调用之前,Angular读取此cookie并通过HTTP头发送其内容;
3. 我们的服务“解密”发送的cookie内容,并验证时间戳是否有效,IP地址是否相同,并根据我们的业务规则确定用户是否有权进行操作或阅读。
目前,我们认为这个设计是一致的,而且如果我们选择正确的算法来创建这个令牌,我们相信这是一种安全的方法。
我们的疑问是:
1. 我们是否会通过不使用具有某种自定义提供者的HTTP身份验证而重新发明轮子?如何做?
2. Spring Security能否帮助我们做到这一点?我们已经阅读了一些文章,但不清楚是否有意义将其用于非Spring应用程序;
3. 我们是否考虑到了此方法可能存在的任何安全漏洞?
谢谢。任何帮助都会被赞赏。
Filipe
4个回答

2

Spring Security能解决问题,而且额外赠送所有Spring Security功能。

Token方法非常好,以下是如何使用spring-security保护API的步骤:

实现AuthenticationEntryPoint并将commence方法设置为401,而不是重定向3XX,具体如下:

httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Access Denied");
  • 创建一个TokenProcessingFilter,继承UsernamePasswordAuthenticationFilter的功能,重写doFilter()方法,从请求头中提取令牌,按以下步骤验证和认证令牌:

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                HttpServletRequest httpRequest = this.getAsHttpRequest(request);
                String authToken = this.extractAuthTokenFromRequest(httpRequest);
                String userName = TokenUtils.getUserNameFromToken(authToken);
                if (userName != null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(userName);

                    if (TokenUtils.validateToken(authToken, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
                chain.doFilter(request, response);
            }

你的Spring-security配置将如下所示:

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        private AuthFailure authFailure;

        @Autowired
        private AuthSuccess authSuccess;

        @Autowired
        private EntryPointUnauthorizedHandler unauthorizedHandler;

        @Autowired
        private UserDetailsService userDetailsService;

        @Autowired
        private AuthenticationTokenProcessingFilter authTokenProcessingFilter;

        @Autowired
        public void configureAuthBuilder(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Bean public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .csrf().disable()
                    .sessionManagement()
                       .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Restful hence stateless
                     .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(unauthorizedHandler) // Notice the entry point
                    .and()
                    .addFilter(authTokenProcessingFilter) // Notice the filter
                    .authorizeRequests()
                       .antMatchers("/resources/**", "/api/authenticate").permitAll()                 
                       //.antMatchers("/admin/**").hasRole("ADMIN")
                       //.antMatchers("/providers/**").hasRole("ADMIN") 
                    .antMatchers("/persons").authenticated();
        }

}

-- 最后,您需要另一个用于身份验证和令牌生成的终点。以下是一个Spring MVC示例。
@Controller
@RequestMapping(value="/api")
public class TokenGenerator{
    @Autowired
    @Lazy
    private  AuthenticationManager authenticationManager;

    @Autowired
    private  UtilityBean utilityBean;

    @Autowired
    private  UserDetailsService userDetailsService;


    @RequestMapping(value="/authenticate", method=RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<?> generateToken(@RequestBody EmefanaUser user){
        ResponseEntity<?> response = null;
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserId(),user.getCredential());

        try {
            Authentication authentication = authenticationManager.authenticate(authenticationToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);

            /*
             * Reload user as password of authentication principal will be null
             * after authorization and password is needed for token generation
             */
            UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUserId());
            String token = TokenUtils.createToken(userDetails);
            response = ResponseEntity.ok(new TokenResource(utilityBean.encodePropertyValue(token)));
        } catch (AuthenticationException e) {
            response = ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        return response;
    } 

}

1 生成令牌,2. 后续的API调用应该带有令牌。是的,Spring Security可以做到这一点,您不必在身份验证和授权方面开创新的领域。

  • 希望这可以帮助

TokenUtils在哪个jar包中? - KyelJmD
这里是一个自定义类的 Git 位置 https://github.com/iamiddy/E-mefana/blob/master/e-mefana/src/main/java/com/idrene/emefana/security/TokenUtils.java - iamiddy

1
我晚到了,但这是我的意见。
免责声明:之前的回答是解决此问题的一种可能方法。下一个见解是我在Liferay中实现RESTful API时学到的东西。
如果我正确理解这个问题,那么你在这里有两种情况。第一种情况是您需要创建一个RESTful API,已经登录的用户将调用它。这意味着AJAX调用可能会在客户端渲染门户网站时执行。这里的主要问题是安全性,如何保护您的REST调用。 首先,我认为应该尝试利用使用的任何框架,然后再实现其他东西。Liferay在后端确实使用了Spring,但他们已经实现了安全性。我建议使用Delegate Servlet。 该servlet将执行任何自定义类并将其放置在Liferay的身份验证路径中,这意味着您只需使用PortalUtil.getUser(request),如果为0或null,则用户未经过身份验证。 为了使用委派servlet,您只需要在web.xml文件中对其进行配置。
<servlet>
    <servlet-name>My Servlet</servlet-name>
    <servlet-class>com.liferay.portal.kernel.servlet.PortalDelegateServlet</servlet-class>
    <init-param>
        <param-name>servlet-class</param-name>
        <param-value>com.samples.MyClass</param-value>
    </init-param>
    <init-param>
        <param-name>sub-context</param-name>
        <param-value>api</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
</servlet>

正如您所看到的,我们正在实例化另一个servlet。这个servlet将由PortalDelegateServlet定义。 Delegate Servlet将使用sevlet-class参数的值上的任何类。在那个类中,您可以使用Liferay的Utils仅检查HttpServletRequest对象中是否有有效的用户名,如果有,则用户可以继续。 现在,您访问它的方式是委托servlet使用sub-context的值来知道您从URL引用的是哪个类。因此,在这个例子中,您将通过访问https://my.portal/delegate/api来访问com.samples.MyClass。 'delegate'部分始终存在,URL的第二部分是我们在init-param中定义的。请注意,您只能为子上下文定义一级URI,即您不能设置/ api / v2.0 /作为子上下文。 从那时起,您可以根据需要在servlet类上执行任何操作并处理REST URI的解析。

您还可以使用spring的Dispatcher类作为Delegate Servlet将调用的类,并设置spring servlet,从而拥有url注释映射。

重要的是要知道这只适用于RESTful或资源服务,因为委托Servlet不知道如何处理视图的渲染。
第二种情况是能够从任何外部应用程序调用此RESTful API(无论它们有什么实现)。 这是完全不同的问题,我需要参考iamiddy的答案,并使用Spring的身份验证令牌可能是一种不错的方式。
另一种方法是通过将未经授权的用户发送到登录页面或类似页面来在您的servlet类中处理它们。 一旦他们成功登录Liferay的Utils应该识别请求的已认证用户。 如果您想在外部应用程序中执行此操作,则需要模拟基于表单的登录,并在整个时间内使用相同的cookie jar。 虽然我没有尝试过这样做,但理论上应该可以。 再说一遍,理论上,共产主义是可行的。
希望这能帮助其他可怜的灵魂。

0

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接