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

IT小君   2023-09-16T20:02:29

我们正在构建一些通过RESTful API公开的服务。这个API的主要客户是使用Angular JS的Liferay门户,这意味着从客户端(Angular)直接调用我们的服务。

到目前为止,我们已经设计了一种身份验证和授权机制,以确保我们可以“识别请求我们API的已登录用户(Liferay)”。

注意,尽管我们使用的是Liferay,但也可以是任何其他基于Java的应用程序。

我们设计的内容如下:

  1. 当用户在我们的门户登录时,Liferay会创建一个身份验证令牌,其中包括用户登录名(或ID)+客户端IP+时间戳。这个令牌被保存在一个cookie中;
  2. 在每个REST调用之前,Angular会读取这个cookie并通过HTTP头发送其内容;
  3. 我们的服务“解密”发送的cookie内容,并验证时间戳是否有效,IP是否相同,并根据我们的业务规则,判断用户是否有权限进行操作或阅读。

目前,我们认为这个设计是一致的,并且根据我们选择创建这个令牌的算法,我们相信这是一种安全的方法。

我们的疑问是:

  • 我们是否在重新发明轮子,而不使用带有某种自定义提供程序的HTTP身份验证?如何做到这一点?
  • Spring Security能帮助我们吗?我们已经阅读了一些相关文章,但不清楚是否可以在非Spring应用程序中使用它;
  • 我们是否没有考虑到任何安全漏洞?

提前感谢。任何帮助都将不胜感激。

Filipe

评论(4)
IT小君

Spring security解决了问题描述,并且作为额外的奖励,你将免费获得所有Spring security的功能。

令牌方法很棒,以下是如何使用spring-security保护你的API的方法 实现AuthenticationEntryPoint并将commence方法设置为401而不是重定向3XX,如下所示

httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"拒绝访问");
  • 创建一个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) // 因此是无状态的
                     .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(unauthorizedHandler) // 注意入口点
                    .and()
                    .addFilter(authTokenProcessingFilter) // 注意过滤器
                    .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 {
            2023-09-16T20:03:25  
              回复
IT小君

我来晚了,但是我有一点意见。

免责声明:前面的答案是解决这个问题的一种可能方式。下一个见解是我在Liferay实施RESTful API时学到的。

如果我理解问题正确,那么你在这里有两种情况。第一种情况是你需要创建一个RESTful api,已经登录的用户将调用它。这意味着AJAX调用可能会在客户端的渲染过程中执行。这里的主要问题是安全性,如何保护你的REST调用。 首先,我认为应该尝试利用所使用的任何框架,而不是实现其他东西。Liferay在后端确实使用了Spring,但他们已经实现了安全性。我建议使用Delegate Servlet。 这个servlet将执行任何自定义类,并将其放在Liferay的身份验证路径中,这意味着你只需要使用PortalUtil.getUser(request),如果它的值为0或null,则用户未经身份验证。 为了使用delegate 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将使用servlet-class参数的值来确定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或资源服务,因为Delegate Servlet不知道如何处理视图的渲染。

你的第二种情况是能够从任何外部应用程序(无论其实现方式如何)调用这个RESTful API。这是一个完全不同的问题,我将参考< a href="https://stackoverflow.com/a/28333815">iamiddy的答案,使用Spring的身份验证令牌可能是一个不错的方法。

另一种做法是,在你的servlet类中处理未经授权的用户,并将他们发送到登录页面或类似的地方。一旦他们成功登录,Liferay的Utils应该通过请求识别出已经认证的用户。如果你想在外部应用程序中做到这一点,那么你需要模拟一个基于表单的登录,并在整个时间内使用同一个cookie jar。尽管我没有尝试过这个,但理论上应该可以。再说一遍,理论上,共产主义也是有效的。

希望这对其他一些可怜的灵魂有所帮助。

2023-09-16T20:04:10   回复
IT小君

请看一下单点登录Spring Security OAuth2令牌认证。

这是一个例子:sso-with-oauth2-angular-js-and-spring-security

请注意,Spring 4.2可能有一些方便的CORS支持

2023-09-16T20:04:17   回复
IT小君

我当前的评分无法给别人的回答点赞,但上面的回答可能是正确的方向。 听起来你需要调查的是一个叫做CORS的东西,它提供了跨站点脚本的安全性。很抱歉我还不太了解它的工作原理(我也处于同样的情况),但这是这份NSA关于REST的文件的主要主题。

对于Spring来说,也许可以从这里开始?

2023-09-16T20:04:26   回复