欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《Spring Security3》第八章第二部分翻译(OpenID用户的注册)

程序员文章站 2022-05-08 16:32:07
...

 

OpenID用户的注册问题

         请使用我们前面的技术来测试Yahoo! OpenID——例如,https://me.yahoo.com/pmularien。你会发现它并不好用,像其它OpenID提供者那样。这带出了OpenID结构的一个很重要的问题,并体现出了启用OpenID用户注册的重要性。

OpenID标识是如何处理的

         Yahoo!返回的实际OpenID类似于如下的样子:https://me.yahoo.com/pmularien#9a466。在OpenID术语中,用户在登录框中输入的标识符被称为用户提供的标识符(user-supplied identifier)。这个标识符可能并不对应唯一的用户标识符(用户的声明标识符,claimed identifier),但是作为鉴别所有者的一部分,OpenID提供者将会把用户的输入转换成提供者能真正证明用户拥有的标识符。(译者注:即用户记住的标识符与OpenID Provider返回的标识符可能并不一致)。

         OpenID发现协议以及OpenID提供者本身实际上能够很奇妙地基于OpenID认证请求计算出用户是谁。例如,尝试输入OpenID提供者(如www.yahoo.com)的名字在OpenID登录框中——你会看到一个不同的界面让你输入OpenID,因为你并没有在登录框中提供唯一的OpenID。很聪明!关注这个还有更多OpenID的规范,可以OpenID组织站点的规范页面(在开发者页面中),http://openid.net/developers/。】

         一旦用户能够提供他拥有的声明标识符,OpenID提供者将会返回给请求拥有一个声明标识符的正规版本,这被叫做OpenID提供者本地标识符(OpenID Provider Local IdentifierOP-Local Identifier)。这是OpenID提供者表明用户的最终唯一标识符,也是到OpenID提供者的认证请求的返回值。所以,这就是JBCP Pets应该存储并用来区分用户的标识符。

         Spring Security处理OpenID登录请求的流程如下图所示:


《Spring Security3》第八章第二部分翻译(OpenID用户的注册)
            
    
    博客分类: Spring SecurityJ2EE Spring Securityjava EE安全翻译 
 o.s.s.openid.OpenIDAuthenticationFilter负责监听/j_spring_openid_security_check这个URL并响应用户的登录请求,类似于UsernamePasswordAuthenticationFilter/j_spring_security_check URL所作的那样。从这个图我们可以看到o.s.s.openid.OpenID4JavaConsumer委托openid4java库构建最终重定向用户到OpenID提供者的URLopenid4java库(通过org.openid4java.consumer.ConsumerManager)还负责查找提供者,对此我们前面有所描述。

         这个过滤器实际上负责OpenID认证的两个阶段——即格式化到OpenID提供者的重定向以及处理提供者的认证响应。OpenID提供者的响应是一个简单的GET请求,带有一系列定义良好的域,它们会被openid4java库处理并认证。你并会直接处理这些域,其中一些重要的如下:

        

域名

描述

openid.op_endpoint

OpenID提供者用来校验的URL

openid.claimed_id

用户提供的OpenID声明标识符

openid.response_nonce

提供者计算出的当前时间,用来创建签名

openid.sig

OpenID的响应签名

openid.association

根据请求者生成的一次性使用的相关数据,被用来计算签名并确定响应的合法性

openid.identifier

OP-Local identifier

 

         我们将会了解这些域怎样用于校验响应的合法性。让我们看一下处理提供者的OpenID响应的几个相关者:


《Spring Security3》第八章第二部分翻译(OpenID用户的注册)
            
    
    博客分类: Spring SecurityJ2EE Spring Securityjava EE安全翻译 
 我们可以看到用户在提交凭证到OpenID提供者站点后,被重定向到/j_spring_openid_security_checkOpenIDAuthenticationFilter进行一些基本的检查以判断这个请求是OpenID请求(从JBCP Pets登录表达发起)还是一个提供者的合法OpenID响应。

       一旦确定这个请求是OpenID响应,要进行一系列复杂的校验以保证响应的正确性和可靠性(参考随后的Is OpenID secure?章节以了解更多细节)。OpenID4JavaConsumer最终会返回一个填充不完整的o.s.s.openid.OpenIDAuthenticationToken对象,而它会被过滤器用来确定对响应的初始校验是否成功。这个token会被传递到AuthenticationManager中,而AuthenticationManager像处理其它Authentication对象那样处理它。

         o.s.s.openid.OpenIDAuthenticationProvider最终负责跟本地存储进行的校验(如JdbcDaoImpl)。要记住的重要一点是,在数据存储中期望得到的包含OP-Local Identifier的用户名,它与用户提供的标识符并不一定一致——这是进行OpenID注册的问题关键。从这以下的处理流程与传统的用户名/密码认证很相似,最重要的是要从UserDetailsService取到正确的组和角色分配。

使用OpenID实现注册

         对于启用了OpenID功能的JBCP Pets站点来说,用户要创建一个账号就要首先证明他们拥有自己所声明的标识符。所以,我们允许用户提供一个OpenID注册。我们已经添加了为标准的用户名和密码认证添加了注册流程,你可以通过页头上的“Registration”进行尝试。让我们扩展这个注册流程以允许用户使用OpenID来注册。

 

添加OpenID注册选项

         首先,我们需要添加一个简单的表单到注册页上以允许用户输入和校验他们的OpenID标识,这个表单与登录的表单很相似(实际上,完全一致)。在registration.jsp中添加以下代码:

 

<h1>Or, Register with OpenID</h1>
<p>
  Please use the form below to register your account with OpenID.
</p>
<form action="j_spring_openid_security_check" method="post">
  <label for="openid_identifier">Login</label>:
  <input id="openid_identifier" name="openid_identifier" size="50" 
maxlength="100" type="text"/>
  <img src="images/openid.png" alt="OpenID"/>
  <br />
  <input type="submit" value="Login"/>
</form>

 这个表单实际上与登录页的表单完全一样。那我们怎样区分登录和注册请求呢?

区分登录和注册请求

         我们选择了一种很简单的方法来区分登录和注册请求。如果用户进行了一个成功的OpenID认证尝试,但是在我们的数据库中还没有他的账号,那我们就假设这是一个注册请求并把它加到数据库中。当然还可以完善这种方式(如,在用户创建账号之前显示确认信息),但是对于我们的例子来说,这就足够了。

         我们会扩展标准的AuthenticationFailureHandler,在com.packtpub.springsecurity.security.OpenIDAuthenticationFailureHandler类中,如下:

 

package com.packtpub.springsecurity.security;
// imports omitted
public class OpenIDAuthenticationFailureHandler extends
  SimpleUrlAuthenticationFailureHandler {
  @Override
  public void onAuthenticationFailure(HttpServletRequest request,
      HttpServletResponse response, AuthenticationException exception)
      throws IOException, ServletException {
    if(exception instanceof UsernameNotFoundException && exception.getAuthentication() instanceof  OpenIDAuthenticationToken&& ((OpenIDAuthenticationToken)exception.getAuthentication()).getStatus().equals(OpenIDAuthenticationStatus.SUCCESS)) {
      DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
  request.getSession(true).setAttribute("USER_OPENID_CREDENTIAL", ((UsernameNotFoundException)exception).getExtraInformation());
      // redirect to create account page
      redirectStrategy.sendRedirect(request, response, "/registrationOpenid.do");
    } else {
      super.onAuthenticationFailure(request, response, exception);
    }
  }
}

 我们可以看到这个代码扩展了父类的默认行为,在满足以下条件时将重定向用户到registrationOpenid.do

l  用户遇到了UsernameNotFoundException

l  用户已经被OpenID提供者成功认证(这通过检查OpenIDAuthenticationTokenOpenIDAuthenticationStatus属性值)。

这个代码将OpenID提供者返回的OP-Local Identifier值设置到session中,所以在被重定向到OpenID注册URL时还能取到。

配置自定义的认证失败处理器

         我们需要配置这个认证失败处理器,只需要简单调整dogstore-security.xml中的<openid-login>声明:

 

<openid-login authentication-failure-handler-ref="openIdAuthFailureHandler">
<!-- The corresponding bean can be declared in dogstore-base.xml:-->
<bean id="openIdAuthFailureHandler" 
class="com.packtpub.springsecurity.security.OpenIDAuthenticationFailureHandler">
  <property name="defaultFailureUrl" value="/login.do"/>
</bean>

 defaultFailureUrl是用户遇到真正的登录失败时(如提供了非法的凭证信息),用户被重定向到的地址。

添加OpenID注册功能到控制器上

         处理基于OpenID的注册很容易,添加到LoginLogoutController上就可以,这上面已经有了标准的用户名和密码注册:

 

@RequestMapping(method=RequestMethod.GET,value="/registrationOpenid.do")
public String registrationOpenId(HttpServletRequest request) {
  String userId = (String) request.getSession().getAttribute("USER_OPENID_CREDENTIAL");
  if(userId != null) {
    userService.createUser(userId, "unused", null);
    setMessage(request, "Your account has been created. Please log in using your OpenID.");
     return "redirect:login.do";
  } else {
    setMessage(request, "Please register using your OpenID.");
    return "redirect:registration.do";
  }
}

          接下来,我们要修改IuserService接口和UserServiceImpl实现来建立一个简单的createUser方法:

 

 

@Service
public class UserServiceImpl implements IUserService {
  @Autowired
  CustomJdbcDaoImpl jdbcDao;
// existing code omitted
  @Override
  public void createUser(String username, String password, String 
email) {
    jdbcDao.createUser(username, password, email);
  }
}

 你会发现我们也修改了@Autowired注解以明确引用CustomJdbcDaoImpl,我们需要在这个类中实现一个自定义的createUser,如下:

 

@Transactional
public void createUser(String username, String password, String email) 
{
  getJdbcTemplate().update("insert into users(username, password, enabled, salt) values (?,?,true,CAST(RAND()*1000000000 AS varchar))", username, password);
  getJdbcTemplate().update("insert into group_members(group_id, username) select id,? from groups where group_name='Users'", username);
}

 你可能对SQL感到熟悉——这就是我们在第四章:凭证安全存储中原来初始化用户的。还记得我们创建DatabasePasswordSecurerBean再启动时为密码加salt?有了createUser方法,我们可以移除启动SQL了并且使用Java来初始化用户——你觉得我们应该怎样写代码来完成它呢?你为什么不试一试呢——这是一个很有效的练习来测试你对这方法的知识。

         如果我们不是使用带salt域的自定义user表,我们可以简单的将CustomJdbcDaoImpl改为继承自JdbcUserDetailsManager(就像我们在第四章讨论的那样)并使用已经为我们实现的createUser方法:

 

public class CustomJdbcDaoImpl 
  extends JdbcUserDetailsManager 
  implements IChangePassword {

 这会需要对UserServiceImpl做一些小的修改:

 

 

@Override
public void createUser(String username, String password, String email) 
{
  GrantedAuthority roleUser = new GrantedAuthorityImpl("ROLE_USER");
  UserDetails user = new User(username, password, true, true, true, true, Arrays.asList(roleUser));
  jdbcDao.createUser(user);
}

 你可以看到有两种不同的方式注册用户,自定义和内置的。每种方法都能有效的处理OpenID注册问题。尽可以体验实例应用并选择你喜欢的方法。

         一旦用户通过我们的IuserService功能建立,用户被重定向到首页并且可以登录了。如果你想增强用户体验,我们可以做一些代码修改,保持OpenIDAuthenticationToken到重定向并自动认证用户。

 

【注意,OP-Local identifiers可能会很长——实际上,OpenID 2.0规范并没有提供OP-Local identifier的一个最大长度。Spring Security默认的JDBC数据库模式提供了一个相对很小的用户名列(你可能会记得我们将它从默认值扩展到了100个字符)。取决于你的需要,你可能愿意进一步扩展用户名列以容纳长的标识,或者实现OpenID处理链上的子类(如OpenIDAuthenticationProviderUserDetailsService)以正确处理太长的标识符。这可能包括将用户名拆为多列或存储被删节的URL和完整URL的哈希值。

要记住的是,认证不仅仅是基于OpenID标识数据库中的用户。有一些使用OpenID的站点比这更进一步,允许关联OpenID标识符和要进行认证的用户名(例如,允许多个OpenID关联相同的用户账号)。根据用户名提取出OpenID对于那些拥有不同提供者的多个OpenID且想在站点中使用的用户来说会有用——尽管这在一定程度上违背了OpenID的初衷,但它确实存在,你在设计使用OpenID站点的时候需要记住。】

 

         除了凭证管理和中心认证,OpenID的另一个承诺就是对用户来说在一个地方管理其个人信息并对特定的站点有选择的释放信息。这可能会提供能够丰富的注册体验。让我们看一下属性交换期望如何解决这个问题。

  • 《Spring Security3》第八章第二部分翻译(OpenID用户的注册)
            
    
    博客分类: Spring SecurityJ2EE Spring Securityjava EE安全翻译 
  • 大小: 43.6 KB
  • 《Spring Security3》第八章第二部分翻译(OpenID用户的注册)
            
    
    博客分类: Spring SecurityJ2EE Spring Securityjava EE安全翻译 
  • 大小: 81 KB