用Java设计确认令牌

时间:2013-11-16 04:56:33

标签: java ruby-on-rails mongodb devise bcrypt

似乎无法创建一种从Java为Devise插入用户的功能方法。目前有以下领域:     “_ID”,     “访问权限”,     “confirmation_sent_at”     “confirmation_token”     “confirmed_at”     “电子邮件”,     “encrypted_pa​​ssword”     “sign_in_count”

我能够插入一个计为用户的文档。问题是,当我去: http://www.mysite.com:3000/users/confirmation?confirmation_token=TOKENHERE

我收到一条消息,说它无效。

编辑1: 当我重新发送此用户(生成新令牌)的确认说明时,用户可以登录。这证实了我对令牌成为问题的怀疑。如何将Devise的令牌生成器移植到Java?

编辑2: 当我在网站上注册时,它说我应该检查确认链接。但是,如果我进入Mongo shell,请手动取出确认令牌并将其粘贴到site.com/users/confirmation?confirmation_token=然后它不起作用!但是,如果我确实使用了我发送的确认链接,它就可以了。如何从Java创建一个VALID标记。请帮忙!

2 个答案:

答案 0 :(得分:0)

对于此quoestion,您应该参考this stackoverflow answerprotect_from_forgery的Rails API。

简短的回答是禁用控制器中的伪造保护,但这会使您的应用程序容易受到CSRF攻击:

skip_before_action :verify_authenticity_token

更好的方法是使用JSON或XML请求进行身份验证,因为这些请求不受CSRF保护。您可以找到设计here的解决方案。

修改

Monkey patch设计用于保存未编码的确认令牌。在你的config / initializers / devise.rb

module Devise
  module Models
    module Confirmable
      def generate_confirmation_token
        raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
        @raw_confirmation_token = raw
        self.my_unencoded_column = raw # Patch
        self.confirmation_token = enc
        self.confirmation_sent_at = Time.now.utc
      end
    end
  end
end

答案 1 :(得分:0)

如果其他人发现自己试图让一个java或scala应用程序与rails应用程序共存,我就破解了以下内容。它在scala但使用java apis所以应该很容易阅读。据我所知,它复制了Devise的行为,如果我点击rails app中的确认链接与原始令牌rails / devise生成相同的编码字符串。

import java.security.spec.KeySpec
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Mac
import javax.xml.bind.DatatypeConverter
import java.util.Base64 

// copy functionality from Rails Devise
object TokenGenerator {

  // sample values 9exithzwZ8P9meqdVs3K => 54364224169895883e87c8412be5874039b470e26e762cb3ddc37c0bdcf014f5
  //              5zNMi6egbyPoDUy2t3NY => 75bd5d53aa36d3fc61ac186b4c6e2be8353e6b39536d3cf846719284e05474ca

  private val deviseSecret = sys.env("DEVISE_SECRET")
  private val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
  val encoder = Base64.getUrlEncoder()

  case class TokenInfo(raw: String, encoded: String)

  def createConfirmationToken: TokenInfo = {
    // copy behavior from rails world. Don't know why it does this
    val replacements = Map('l' -> "s", 'I' -> "x", 'O' -> "y", '0' -> "z")

    // make a raw key of 20 chars, doesn't seem to matter what they are, just need url valid set
    val bytes = new Array[Byte](16)
    scala.util.Random.nextBytes(bytes)
    val raw = encoder.encodeToString(bytes).take(20).foldLeft(""){(acc, x) => acc ++ replacements.get(x).getOrElse(x.toString)}
    TokenInfo(raw, digestForConfirmationToken(raw))
  }

  private def generateKey(salt: String): Array[Byte] = {
    val iter = 65536
    val keySize = 512
    val spec = new PBEKeySpec(deviseSecret.toCharArray, salt.getBytes("UTF-8"), iter, keySize)
    val sk = factory.generateSecret(spec)
    val skspec = new SecretKeySpec(sk.getEncoded, "AES")
    skspec.getEncoded
  }

  def sha256HexDigest(s: String, key: Array[Byte]): String = {
    val mac = Mac.getInstance("HmacSHA256")
    val keySpec = new SecretKeySpec(key, "RAW")
    mac.init(keySpec)
    val result: Array[Byte] = mac.doFinal(s.getBytes())
    DatatypeConverter.printHexBinary(result).toLowerCase
  }

  private def getDigest(raw: String, salt: String) = sha256HexDigest(raw, generateKey(salt))

  // devise uses salt "Devise #{column}", in this case its confirmation_token
  def digestForConfirmationToken(raw: String) = getDigest(raw, "Devise confirmation_token")
}