搜索词>>jwt 耗时0.1080
  • java编程之java jwt token什么是JWT?(一)

    本文讲解什么是JWT,JWT的构成和JWT算法?,Java编程,JWT<h2>一、什么是JWT?了解JWT,认知JWT</h2>   首先jwt其实是三个英语单词JSON Web Token的缩写。通过全名你可能就有一个基本的认知了。token一般都是用来认证的,比如我们系统中常用的用户登录token可以用来认证该用户是否登录。jwt也是经常作为一种安全的token使用。<br /> <br /> <strong>JWT的定义:</strong><br />   JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。<br /> <strong>JWT特点:</strong> <ul> <li> <p><strong>简洁(Compact)</strong>: 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快</p> </li> <li> <p><strong>自包含(Self-contained)</strong>:负载中包含了所有用户所需要的信息,避免了多次查询数据库</p> </li> </ul> <h2>二、JWT构成或者说JWT是什么样的?</h2> <strong>2.1.JWT结构</strong><br /> JWT主要包含三个部分之间用英语句号'.'隔开 <ol> <li>Header 头部</li> <li>Payload 负载</li> <li>Signature 签名</li> </ol> <strong>注意,顺序是 header.payload.signature</strong><br /> 最终的结构有点像这样: <pre> <code>leftso.com.blog </code></pre> 当然真实的jwt不可能是这么简单的明文<br /> <br /> <strong>2.2.JWT的头部(Header)</strong><br /> 在header中通常包含了两部分:token类型和采用的加密算法。如下: <pre> <code class="language-json">{ "alg": "HS256", "typ": "JWT" } </code></pre> 上面的JSON内容指定了当前采用的加密方式为HS256,token的类型为jwt<br /> <br /> 将上面的内容进行base64编码,可以得到我们JWT的头部,编码后如下:<br /> (本站提供了在线的base64编码/解码的工具,可供读者测试) <pre> <code>ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=</code></pre> <br /> <strong>2.3.JWT的负载(Payload)</strong><br /> 负载(Payload)为JWT的第二部分。JWT的标准所定义了一下几个基本字段 <ol> <li><code>iss</code>: 该JWT的签发者</li> <li><code>sub</code>: 该JWT所面向的用户</li> <li><code>aud</code>: 接收该JWT的一方</li> <li><code>exp</code>(expires): 什么时候过期,这里是一个Unix时间戳</li> <li><code>iat</code>(issued at): 在什么时候签发的</li> </ol> <br /> 除了标准定义的字段外,我们还要定义一些我们在业务处理中需要用到的字段,例如用户token一般可以包含用户登录的token或者用户的id,一个简单的例子如下: <pre> <code class="language-json">{ "iss": "Lefto.com", "iat": 1500218077, "exp": 1500218077, "aud": "www.leftso.com", "sub": "leftso@qq.com", "user_id": "dc2c4eefe2d141490b6ca612e252f92e", "user_token": "09f7f25cdb003699cee05759e7934fb2" }</code></pre> 上面的user_id、user_token都是我们自己定义的字段<br /> <br /> 现在我们需要将负载这整个部分进行base64编码,编码后结果如下: <pre> <code>ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9</code></pre> <br /> <strong>2.4.</strong><strong>Signature(签名)</strong><br /> 签名其实是对JWT的头部和负载整合的一个签名验证<br /> 首先需要将头部和负载通过.链接起来就像这样:header.Payload,上述的例子链接起来之后就是这样的: <pre> <code>ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9</code></pre> <br /> 由于HMacSHA256加密算法需要一个key,我们这里key暂时用leftso吧<br /> <br /> 加密后的内容为: <pre> <code>686855c578362e762248f22e2cc1213dc7a6aff8ebda52247780eb6b5ae91877</code></pre> <br /> 其实加密的内容也就是JWT的签名,类似我们对某个文件进行MD5加密然后接收到文件进行md5对比一样.只是这里的HMacSHA256算法需要一个key,当然这个key应该是使用者和接收者都知道的。<br /> <br /> 对上面的签名内容进行base64编码得到最终的签名 <pre> <code>Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==</code></pre> <br /> <strong>2.5最终的JWT</strong> <pre> <code>ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9.Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==</code></pre> <br /> 通过上面的一个简单的说明您是否对JWT有一个简单额认知了呢?接下来我将讲解JWT在Java编程中的使用<br /> <br /> 未完待续中...<br /> <br />  
  • java编程之java jwt token使用autho0-jwt框架使用(二)

    java编程之java jwt token使用,autho0的Java-jwt框架使用,java编程,java-jwt<h2>一、前言</h2> Java编程中使用jwt,首先你必须了解jwt是什么,长什么样子。如果你不了解可以先去本站另外一篇博客<a href="http://www.leftso.com/blog/220.html" rel="" target="_blank" >什么是JWT?</a><br />   <h2>二、Java编程中jwt框架选择</h2> 在Java编程中,实现jwt标准的有很多框架,本博客采用的框架是auth0的java-jwt版本为3.2.0 <h2>三、什么是Java-JWT</h2> auth0的java-jwt是一个JSON WEB TOKEN(JWT)的一个实现。 <h2>四、安装下载相关依赖</h2> 如果你是采用<strong>maven</strong>的方式,在你的项目pom.xml文件中添加以下java-jwt的依赖片段: <pre> <code class="language-xml"><dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency></code></pre> 如果你是采用<strong>Gradle</strong>的方式,则添加以下内容: <pre> <code>compile 'com.auth0:java-jwt:3.2.0'</code></pre> <h2>五、java-jwt已经实现的算法</h2> 该库使用以下算法实现JWT验证和签名: <table class="table table-bordered table-hover" > <thead> <tr> <th>JWS</th> <th>算法</th> <th>介绍</th> </tr> </thead> <tbody> <tr> <td>HS256</td> <td>HMAC256</td> <td>HMAC with SHA-256</td> </tr> <tr> <td>HS384</td> <td>HMAC384</td> <td>HMAC with SHA-384</td> </tr> <tr> <td>HS512</td> <td>HMAC512</td> <td>HMAC with SHA-512</td> </tr> <tr> <td>RS256</td> <td>RSA256</td> <td>RSASSA-PKCS1-v1_5 with SHA-256</td> </tr> <tr> <td>RS384</td> <td>RSA384</td> <td>RSASSA-PKCS1-v1_5 with SHA-384</td> </tr> <tr> <td>RS512</td> <td>RSA512</td> <td>RSASSA-PKCS1-v1_5 with SHA-512</td> </tr> <tr> <td>ES256</td> <td>ECDSA256</td> <td>ECDSA with curve P-256 and SHA-256</td> </tr> <tr> <td>ES384</td> <td>ECDSA384</td> <td>ECDSA with curve P-384 and SHA-384</td> </tr> <tr> <td>ES512</td> <td>ECDSA512</td> <td>ECDSA with curve P-521 and SHA-512</td> </tr> </tbody> </table> <h2>六、如何使用java-jwt</h2> <strong>6.1.选择一种算法</strong> <p>  算法定义了一个令牌是如何被签名和验证的。它可以用HMAC算法的原始值来实例化,也可以在RSA和ECDSA算法的情况下对密钥对或密钥提供程序进行实例化。创建后,该实例可用于令牌签名和验证操作。</p> <p>在使用RSA或ECDSA算法时,只需要签署JWTs,就可以通过传递null值来避免指定公钥。当您需要验证JWTs时,也可以使用私钥进行操作<br /> <br /> 使用静态的字符密文或者key来获取算法器:<br />  </p> <pre> <code class="language-java">//HMAC Algorithm algorithmHS = Algorithm.HMAC256("secret"); //RSA RSAPublicKey publicKey = //Get the key instance RSAPrivateKey privateKey = //Get the key instance Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);</code></pre> 使用一个key提供者来获取算法:<br />   通过使用KeyProvider,您可以在运行时更改密钥,用于验证令牌签名或为RSA或ECDSA算法签署一个新的令牌。这是通过实现RSAKeyProvider或ECDSAKeyProvider方法实现的: <ul> <li><code>getPublicKeyById(String kid)</code>: 它在令牌签名验证中调用,它应该返回用于验证令牌的密钥。如果使用了关键的轮换,例如JWK,它可以使用id来获取正确的轮换键(或者只是一直返回相同的键)。</li> <li><code>getPrivateKey()</code>: 在令牌签名期间调用它,它应该返回用于签署JWT的密钥。</li> <li><code>getPrivateKeyId()</code>:在令牌签名期间调用它,它应该返回标识由getPrivateKey()返回的键的id的id。这个值比JWTCreator.Builder和keyid(String)方法中的值更受欢迎。如果您不需要设置孩子的值,就避免使用KeyProvider实例化算法。</li> </ul> 下面的代码片段将展示如何使用: <pre> <code class="language-java">final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); final RSAPrivateKey privateKey = //Get the key instance final String privateKeyId = //Create an Id for the above key RSAKeyProvider keyProvider = new RSAKeyProvider() { @Override public RSAPublicKey getPublicKeyById(String kid) { //Received 'kid' value might be null if it wasn't defined in the Token's header RSAPublicKey publicKey = jwkStore.get(kid); return (RSAPublicKey) publicKey; } @Override public RSAPrivateKey getPrivateKey() { return privateKey; } @Override public String getPrivateKeyId() { return privateKeyId; } }; Algorithm algorithm = Algorithm.RSA256(keyProvider); //Use the Algorithm to create and verify JWTs.</code></pre> <br /> <em>提示:对于使用JWKs的简单的键轮换,可以尝试JWKs-rsa-java库。</em><br /> <br /> <strong>6.2.创建一个签名的JWT token</strong><br /> 首先需要通过调用<code>jwt.create()</code>创建一个<code>JWTCreator</code>实例 <ul> <li>例如使用 <code>HS256算法:</code></li> </ul> <pre> <code class="language-java">try { Algorithm algorithm = Algorithm.HMAC256("secret"); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }</code></pre> <ul> <li>例如使用<code>RS256算法:</code></li> </ul> <pre> <code class="language-java">RSAPublicKey publicKey = //Get the key instance RSAPrivateKey privateKey = //Get the key instance try { Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }</code></pre> 如果Claim不能转换为JSON,或者在签名过程中使用的密钥无效,那么将会抛出<code>JWTCreationException</code>异常。<br /> <br /> <strong>6.3.验证令牌</strong><br />   首先需要通过调用jwt.require()和传递算法实例来创建一个JWTVerifier实例。如果您要求令牌具有特定的Claim值,请使用构建器来定义它们。方法build()返回的实例是可重用的,因此您可以定义一次,并使用它来验证不同的标记。最后调用verifier.verify()来验证token <ul> <li>例如使用 <code>HS256算法的时候:</code></li> </ul> <pre> <code class="language-java">String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { Algorithm algorithm = Algorithm.HMAC256("secret"); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTVerificationException exception){ //Invalid signature/claims }</code></pre> <ul> <li>例如使用 <code>RS256算法的时候:</code></li> </ul> <pre> <code class="language-java">String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; RSAPublicKey publicKey = //Get the key instance RSAPrivateKey privateKey = //Get the key instance try { Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException exception){ //Invalid signature/claims }</code></pre> 如果令牌有一个无效的签名,或者没有满足Claim要求,那么将会抛出JWTVerificationException异常<br /> <br /> <strong>6.4.jwt时间的验证</strong><br /> JWT令牌可能包括可用于验证的DateNumber字段: <ul> <li>这个令牌发布了一个过期的时间 <code>"iat" < TODAY</code></li> <li>这个令牌还没过期 <code>"exp" > TODAY</code> and</li> <li>这个令牌已经被使用了. <code>"nbf" > TODAY</code></li> </ul> 当验证一个令牌时,时间验证会自动发生,导致在值无效时抛出一个JWTVerificationException。如果前面的任何一个字段都丢失了,那么在这个验证中就不会考虑这些字段。<br /> 要指定令牌仍然被认为有效的余地窗口,在JWTVerifier builder中使用accept回旋()方法,并传递一个正值的秒值。这适用于上面列出的每一项。 <pre> <code class="language-java">JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) // 1 sec for nbf, iat and exp .build();</code></pre> 您还可以为给定的日期声明指定一个自定义值,并为该声明覆盖缺省值。 <pre> <code class="language-java">JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) //1 sec for nbf and iat .acceptExpiresAt(5) //5 secs for exp .build();</code></pre> 如果您需要在您的lib/app中测试此行为,将验证实例转换为basever可视化,以获得verific.build()方法的可见性,该方法可以接受定制的时钟。例如: <pre> <code class="language-java">BaseVerification verification = (BaseVerification) JWT.require(algorithm) .acceptLeeway(1) .acceptExpiresAt(5); Clock clock = new CustomClock(); //Must implement Clock interface JWTVerifier verifier = verification.build(clock);</code></pre> <br /> <strong>6.5解码一个jwt令牌</strong> <pre> <code class="language-java">String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { DecodedJWT jwt = JWT.decode(token); } catch (JWTDecodeException exception){ //Invalid token }</code></pre> 如果令牌有无效的语法,或者消息头或有效负载不是JSONs,那么将会抛出JWTDecodeException异常。<br /> <br /> <strong>6.6JWT头部解析</strong> <h4><br /> Algorithm ("alg")</h4> 返回jwt的算法值或,如果没有定义则返回null <pre> <code class="language-java">String algorithm = jwt.getAlgorithm();</code></pre> <p> </p> <p>Type ("typ")</p> <p>返回jwt的类型值,如果没有定义则返回null(多数情况类型值为jwt)</p> <pre> <code class="language-java">String type = jwt.getType();</code></pre> <p> </p> <p>Content Type ("cty")</p> <p>返回内容的类型,如果没有定义则返回null</p> <pre> <code class="language-java">String contentType = jwt.getContentType();</code></pre> <p><br /> Key Id ("kid")<br /> 返回key的id值,如果没有定义则返回null<br />  </p> <pre> <code class="language-java">String keyId = jwt.getKeyId();</code></pre> <h4>私有的Claims,即自定义字段</h4> 在令牌的头部中定义的附加声明可以通过调用getHeaderClaim() 获取,即使无法找到,也会返回。您可以通过调用claim.isNull()来检查声明的值是否为null。 <pre> <code class="language-java">Claim claim = jwt.getHeaderClaim("owner");</code></pre> 当使用jwt.create()创建一个令牌时,您可以通过调用withHeader()来指定头声明,并同时传递声明的映射。 <pre> <code class="language-java">Map<String, Object> headerClaims = new HashMap(); headerClaims.put("owner", "auth0"); String token = JWT.create() .withHeader(headerClaims) .sign(algorithm); </code></pre> <em>提示:在签名过程之后,alg和typ值将始终包含在Header中。</em><br /> <br /> <strong>6.7JWT的负载(Payload)声明</strong> <h4>Issuer ("iss")</h4> 返回签发者的名称值,如果没有在负载中定义则返回null <pre> <code class="language-java">String issuer = jwt.getIssuer();</code></pre> <h4>Subject ("sub")</h4> 返回jwt所面向的用户的值,如果没有在负载中定义则返回null <pre> <code class="language-java">String subject = jwt.getSubject();</code></pre> <h4>Audience ("aud")</h4> 返回该jwt由谁接收,如果没有在负载中定义则返回null <pre> <code class="language-java">List<String> audience = jwt.getAudience();</code></pre> <h4>Expiration Time ("exp")</h4> 返回该jwt的过期时间,如果在负载中没有定义则返回null <pre> <code class="language-java">Date expiresAt = jwt.getExpiresAt();</code></pre> <h4>Not Before ("nbf")</h4> Returns the Not Before value or null if it's not defined in the Payload. <pre> <code class="language-java">Date notBefore = jwt.getNotBefore();</code></pre> <h4>Issued At ("iat")</h4> 返回在什么时候签发的,如果在负载中没有定义则返回null <pre> <code class="language-java">Date issuedAt = jwt.getIssuedAt();</code></pre> <h4>JWT ID ("jti")</h4> 返回该jwt的唯一标志,如果在负载中没有定义则返回null <pre> <code class="language-java">String id = jwt.getId();</code></pre> <strong>自定义声明</strong><br /> 在令牌有效负载中定义的附加声明可以通过调用getClaims()或 getClaim()和传递声明名来获得。即使无法找到声明,也将会有返回值。您可以通过调用claim.isNull()来检查声明的值是否为null。 <pre> <code class="language-java">Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name Claim claim = claims.get("isAdmin");</code></pre> 或者: <pre> <code class="language-java">Claim claim = jwt.getClaim("isAdmin");</code></pre> 当使用jwt.create()创建一个令牌时,您可以通过调用withClaim()来指定自定义声明,并同时传递名称和值。 <pre> <code class="language-java">String token = JWT.create() .withClaim("name", 123) .withArrayClaim("array", new Integer[]{1, 2, 3}) .sign(algorithm);</code></pre> 您还可以通过调用withClaim()来验证jwt.require()的自定义声明,并传递该名称和所需的值。 <pre> <code class="language-java">JWTVerifier verifier = JWT.require(algorithm) .withClaim("name", 123) .withArrayClaim("array", 1, 2, 3) .build(); DecodedJWT jwt = verifier.verify("my.jwt.token");</code></pre> <em>提示:当前支持的自定义JWT声明创建和验证的类型是:Boolean, Integer, Double, String, Date 和Arrays。</em><br /> <br /> <strong>6.8Claim Class</strong><br /> 索赔类是索赔值的包装器。它允许您将索赔作为不同的类类型。可用的工具方法:<br /> 原始的: <ul> <li><strong>asBoolean()</strong>: 返回布尔值,如果不能转换返回null。</li> <li><strong>asInt()</strong>: 返回整数值,如果不能转换返回null。</li> <li><strong>asDouble()</strong>: 返回 Double 值,如果不能转换则返回null。</li> <li><strong>asLong()</strong>: 返回Long 值,如果不能转换则返回null。</li> <li><strong>asString()</strong>: 返回String值,如果不能转换则返回null。</li> <li><strong>asDate()</strong>: 返回 Date值,如果不能转换则返回null。 必须是一个数字日期 (Unix 系统时间戳). 注意,JWT标准指定所有的数字日期值必须以秒为单位。</li> </ul> <h4>自定义类型和集合:</h4> 要获得作为集合的声明,您需要提供要转换的内容的类类型 <ul> <li>as(class): 返回 Class Type 的解析值. 对于集合,您应该使用asArray和asList方法。</li> <li>asMap(): 返回被转换为 Map<String, Object>的值</li> <li>asArray(class): 返回被转换成元素类型的 Class Type, or null if the value isn't a JSON Array.</li> <li>asList(class): 返回集合元素的 Class Type, or null if the value isn't a JSON Array.</li> </ul> <em>如果不能将值转换为给定的类类型,则会抛出JWTDecodeException异常</em><br /> <br /> <em>翻译的不标准的后续更近,欢迎提供宝贵意见或者翻译,联系leftso@qq.com</em>
  • Spring Boot Security Jwt Authentication详解实战

    引言    在这篇文章中,我们将通过JWT(JSOn Web Token)认证来保护我们的REST API引言    在这篇文章中,我们将通过JWT(JSOn Web Token)认证来保护我们的REST API 。我们将使用基于spring boot maven的配置来开发并保护我们的API,并提供单独的API用于注册并生成令牌。我们将扩展OncePerRequestFilter类,以使用JWT定义我们的自定义认证机制。认证机制可以应用于URL和方法。最后,我们将使用谷歌高级REST客户端测试实现。项目结构 以下是我们将为spring  boot JWT认证而建立的最终项目结构。​JWT认证机制以下类继承了OncePerRequestFilter,确保每个请求调度都有一次执行。该类检查授权头并验证JWT令牌并在上下文中设置验证。这样做可以保护我们的API免受那些没有任何授权令牌的请求。有关哪些资源需要保护以及哪些不可以配置WebSecurityConfig.java JwtAuthenticationFilter.javapublic class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_STRING); String username = null; String authToken = null; if (header != null && header.startsWith(TOKEN_PREFIX)) { authToken = header.replace(TOKEN_PREFIX,""); try { username = jwtTokenUtil.getUsernameFromToken(authToken); } catch (IllegalArgumentException e) { logger.error("an error occured during getting username from token", e); } catch (ExpiredJwtException e) { logger.warn("the token is expired and not valid anymore", e); } catch(SignatureException e){ logger.error("Authentication Failed. Username or Password not valid."); } } else { logger.warn("couldn't find bearer string, will ignore the header"); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req)); logger.info("authenticated user " + username + ", setting security context"); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(req, res); } } 以下是用于生成身份验证令牌以及从令牌中提取用户名的util类。这里是我们需要url的配置,例如/ token / *和/ signup / *是公开可用的,其余url是受限于公共访问。JwtTokenUtil.java@Component public class JwtTokenUtil implements Serializable { public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public T getClaimFromToken(String token, Function claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(SIGNING_KEY) .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } public String generateToken(User user) { return doGenerateToken(user.getUsername()); } private String doGenerateToken(String subject) { Claims claims = Jwts.claims().setSubject(subject); claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))); return Jwts.builder() .setClaims(claims) .setIssuer("http://devglan.com") .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000)) .signWith(SignatureAlgorithm.HS256, SIGNING_KEY) .compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return ( username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } } 以下是我们在上面的实现中使用的常量。Constants.javapublic class Constants { public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60; public static final String SIGNING_KEY = "devglan123r"; public static final String TOKEN_PREFIX = "Bearer "; public static final String HEADER_STRING = "Authorization"; }  Spring Boot安全配置现在让我们定义我们通常的spring引导安全配置。我们注入了userDetailsS​​ervice以从数据库中获取用户凭据。在这里,注释@EnableGlobalMethodSecurity启用了方法级别的安全性,您可以使用注释(例如@Secured)注释您的方法,以在方法级别提供基于角色的身份验证。 WebSecurityConfig.java@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource(name = "userService") private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(encoder()); } @Bean public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationFilter(); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable(). authorizeRequests() .antMatchers("/token/*").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); } @Bean public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); } }以下是暴露在代表用户创建令牌的控制器,如果您注意到WebSecurityConfig.java我们已将此url配置为不进行身份验证,以便用户可以使用有效凭据生成JWT令牌。 AuthenticationController.java@RestController @RequestMapping("/token") public class AuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserService userService; @RequestMapping(value = "/generate-token", method = RequestMethod.POST) public ResponseEntity register(@RequestBody LoginUser loginUser) throws AuthenticationException { final Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginUser.getUsername(), loginUser.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); final User user = userService.findOne(loginUser.getUsername()); final String token = jwtTokenUtil.generateToken(user); return ResponseEntity.ok(new AuthToken(token)); } } 我们有非常简单的REST Apis公开测试用途。以下是实现。@RestController public class UserController { @Autowired private UserService userService; @RequestMapping(value="/user", method = RequestMethod.GET) public List listUser(){ return userService.findAll(); } @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public User getOne(@PathVariable(value = "id") Long id){ return userService.findById(id); } } 以下是我们的实体类。User.java@Entity public class User { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @Column private String username; @Column @JsonIgnore private String password; @Column private long salary; @Column private int age; }默认脚本以下是在应用程序启动时插入的插入语句。INSERT INTO User (id, username, password, salary, age) VALUES (1, 'Alex123', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 3456, 33); INSERT INTO User (id, username, password, salary, age) VALUES (2, 'Tom234', '$2a$04$PCIX2hYrve38M7eOcqAbCO9UqjYg7gfFNpKsinAxh99nms9e.8HwK', 7823, 23); INSERT INTO User (id, username, password, salary, age) VALUES (3, 'Adam', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 4234, 45);JWT用户登录 由于我们有默认脚本来预先填充数据库中的数据以供测试,但我们也可以为用户注册公开一个API。使用此API用户可以注册并使用相同的用户名和密码来生成令牌。为此,我们在控制器类中添加了以下方法。@RequestMapping(value="/signup", method = RequestMethod.POST) public User saveUser(@RequestBody UserDto user){ return userService.save(user); } 一旦添加,我们需要删除此URL的限制以供公众访问。为此,请在我们的Spring Boot security config中添加以下行。.antMatchers("/token/*", "/signup").permitAll()现在为了创建用户,我们在UserServiceImpl.java保存数据库中的用户记录方面做了简单的实现。这里要注意的一点是使用bcrypt编码器的密码加密。我们已经自动装配了我们定义为bean的相同编码器WebSecurityConfig.java@Autowired private BCryptPasswordEncoder bcryptEncoder; @Override public User save(UserDto user) { User newUser = new User(); newUser.setUsername(user.getUsername()); newUser.setPassword(bcryptEncoder.encode(user.getPassword())); newUser.setAge(user.getAge()); newUser.setSalary(user.getSalary()); return userDao.save(newUser); }这将显示用户注册过程的以下URL以生成JWT令牌。​测试应用程序我们将使用Advanced REST Client来测试spring boot jwt认证。使用令牌生成AuthToken 访问资源,而不使用令牌访问资源 ​​​提示:项目源码下载spring-boot-jwt-auth.zip
  • Spring Boot Security Oauth2 Jwt Auth 详细实战例子

    引言在本文中,我们将讨论有关Spring启动安全性和JWT令牌的OAUTH2实现以及保护REST API引言在本文中,我们将讨论有关Spring启动安全性和JWT令牌的OAUTH2实现以及保护REST API。在我上一篇Spring Boot Security OAUTH2示例中,我们使用OAUTH2创建了一个示例应用程序,用于使用默认令牌存储进行身份验证和授权,但Spring安全性OAUTH2实现还提供了定义自定义令牌存储的功能。在这里,我们将使用JwtTokenStore创建一个示例spring security OAUTH2应用程序。使用JwtTokenStore作为令牌提供程序允许我们自定义使用TokenEnhancer生成的令牌以添加其他声明。    这个应用程序中的大部分配置与我以前的spring security OAUTH2实现文章非常相似,因此我们可能会避免在最后一个应用程序中构建的一些通用代码和配置。让我们从简单介绍JWT开始,然后我们将深入创建我们的授权服务器,资源服务器,稍后我们将讨论在令牌中添加自定义声明。Json Web Token      JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一个紧凑且独立的方式,用于在各方之间以JSON对象安全地传输信息。一种无状态身份验证机制,因为用户状态永远不会保存在服务器内存中。 JWT令牌由3个部分组成,用点(。)即Header.payload.signature分隔头部包含2部分类型的令牌和散列算法。包含这两个键的JSON结构是Base64Encoded。{ "alg": "HS256", "typ": "JWT" }有效载荷包含claims。主要有三种类型的claims:保留,reserved, public, private。保留的claims是iss(发行人),exp(到期时间),sub(主题),aud(受众)等预定义claims。在private claims中,我们可以创建一些自定义claims,如主题,角色和其他。{ "sub": "Alex123", "scopes": [ { "authority": "ROLE_ADMIN" } ], "iss": "http://devglan.com", "iat": 1508607322, "exp": 1508625322 } 签名确保令牌不会在途中发生变化。例如,如果您想使用HMAC SHA256算法,签名将按以下方式创建:HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) 以下是示例JWT令牌。这是一个带有jwt认证应用程序的完整堆栈spring boot程序,用于使用jwt令牌机制保护REST API。eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBbGV4MTIzIiwic2N.v9A80eU1VDo2Mm9UqN2FyEpyT79IUmhg  项目结构 ​Maven的依赖在这里,spring-security-jwt提供了对JwtTokenStore的支持。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency>授权服务器配置我希望你熟悉OAUTH2架构和授权服务器。我在我上一篇文章OAUTH2中解释了它。下面的配置与我们上一次配置的Spring Boot Security OAUTH2实例非常相似,除了JwtAccessTokenConverter和TokenStore.Here,JwtAccessTokenConverter是在JWT编码的令牌值和OAuth之间转换的帮助器认证信息。我们添加了自定义签名以使JWT令牌更健壮。除了JwtTokenStore,spring安全性还提供了InMemoryTokenStore和JdbcTokenStore。 ClientDetailsServiceConfigurer:定义客户端详细信息服务的配置器。客户详细信息可以初始化,或者您可以参考现有的商店。AuthorizationServerSecurityConfigurer :定义了令牌端点上的安全约束。AuthorizationServerEndpointsConfigurer :定义授权和令牌端点以及令牌服务。ClientDetailsS​​erviceConfigurer可用于定义客户端详细信息服务的内存中或JDBC实现。在此示例中,我们使用的是内存中的实现。AuthorizationServerConfig.java@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { static final String CLIEN_ID = "devglan-client"; static final String CLIENT_SECRET = "devglan-secret"; static final String GRANT_TYPE_PASSWORD = "password"; static final String AUTHORIZATION_CODE = "authorization_code"; static final String REFRESH_TOKEN = "refresh_token"; static final String IMPLICIT = "implicit"; static final String SCOPE_READ = "read"; static final String SCOPE_WRITE = "write"; static final String TRUST = "trust"; static final int ACCESS_TOKEN_VALIDITY_SECONDS = 1*60*60; static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 6*60*60; @Autowired private AuthenticationManager authenticationManager; @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("as466gf"); return converter; } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Override public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { configurer .inMemory() .withClient(CLIEN_ID) .secret(CLIENT_SECRET) .authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT ) .scopes(SCOPE_READ, SCOPE_WRITE, TRUST) .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS). refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()) .authenticationManager(authenticationManager) .accessTokenConverter(accessTokenConverter()); } }资源服务器配置我们的上下文中的资源是我们为粗暴操作公开的REST API。要访问这些资源,必须对客户端进行身份验证。在实时场景中,每当用户尝试访问这些资源时,都会要求用户提供他真实性,一旦用户被授权,他将被允许访问这些受保护的资源。 resourceId: 资源的id(可选,但建议并且将由auth服务器验证,如果存在的话)。因为我们在同一个项目中有资源服务器和服务器实现,所以我们不需要在资源服务器配置中重新定义我们的JwtAccessTokenConverter,否则我们需要在资源服务器中提供类似的JwtAccessTokenConverter实现。在这里,我们已经配置/ users是一个受保护的资源,它需要一个ADMIN角色进行访问。@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ID = "resource_id"; @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(RESOURCE_ID).stateless(false); } @Override public void configure(HttpSecurity http) throws Exception { http. anonymous().disable() .authorizeRequests() .antMatchers("/users/**").access("hasRole('ADMIN')") .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); } }REST API现在让我们使用spring控制器公开一些受保护的REST资源。@RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @RequestMapping(value="/user", method = RequestMethod.GET) public List listUser(){ return userService.findAll(); } @RequestMapping(value = "/user", method = RequestMethod.POST) public User create(@RequestBody User user){ return userService.save(user); } @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE) public String delete(@PathVariable(value = "id") Long id){ userService.delete(id); return "success"; } }以下是验证用户的用户服务实现。@Service(value = "userService") public class UserServiceImpl implements UserDetailsService, UserService { @Autowired private UserDao userDao; public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { User user = userDao.findByUsername(userId); if(user == null){ throw new UsernameNotFoundException("Invalid username or password."); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority()); } private List getAuthority() { return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")); } public List findAll() { List list = new ArrayList<>(); userDao.findAll().iterator().forEachRemaining(list::add); return list; } } 以上用户服务在SecurityConfig.java中配置如下。您可以使用此在线Bcrypt计算器来生成Bcrypt密码。@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource(name = "userService") private UserDetailsService userDetailsService; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(encoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .anonymous().disable() .authorizeRequests() .antMatchers("/api-docs/**").permitAll(); } @Bean public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); } @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; } } 创建用户SQL脚本INSERT INTO User (id, username, password, salary, age) VALUES (1, 'Alex123', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 3456, 33); INSERT INTO User (id, username, password, salary, age) VALUES (2, 'Tom234', '$2a$04$PCIX2hYrve38M7eOcqAbCO9UqjYg7gfFNpKsinAxh99nms9e.8HwK', 7823, 23); INSERT INTO User (id, username, password, salary, age) VALUES (3, 'Adam', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 4234, 45);测试OAUTH2 JWT应用程序首先,运行Application.java作为java程序并切换到POSTMAIN,并通过http:// localhost:8080 / oauth / token发出POST请求以生成标记。在标头中,我们选择了基本身份验证并提供了用户名和密码as devglan-client和devglan-secret。这将导致access_token,token_type,refresh_token,过期等。​现在,我们可以使用相同的标记来访问受保护的资源。​总结在这里,我们讨论了如何使用JWT作为Spring启动安全性OAUTH2实现的令牌提供者。提示:项目源码下载spring-boot-security-oauth2-jwt-example.zip
  • spring boot 入门之security oauth2 jwt完美整合例子-java编程

    spring boot 入门之security oauth2 jwt完美整合例子,Java编程中spring boot框架+spring security框架+spring security oauth2框架整合的例子,并且oauth2整合使用jwt方式存储<h2>一、本文简介</h2> 本文主要讲解Java编程中spring boot框架+spring security框架+spring security oauth2框架整合的例子,并且oauth2整合使用jwt方式存储 <h2>二、学习前提</h2> 首先是讲解oauth2的基本说明:<br /> 推荐查看:<a href="http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html" rel="external nofollow" target="_blank">http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html</a><br /> <br /> 上面的地址可以基本了解下oauth2的原理<br /> <img alt="oauth2授权图" class="img-thumbnail" src="/assist/images/blog/37245915-1b05-45d1-922f-84e258c84b8e.png" /><br /> JWT的认知和基本使用:<br /> 推荐查看:<br /> 1.<a rel="" target="_blank"href="http://www.leftso.com/blog/220.html" rel="" target="_blank">什么是JWT?</a> <br /> 2.<a rel="" target="_blank"href="http://www.leftso.com/blog/221.html" rel="" target="_blank">Java编程中java-jwt框架使用</a> <h2>三、代码编辑</h2> 开始代码:<br /> 认证服务:<br /> 几个核心的配置类:<br /> <strong>AuthorizationServerConfiguration.java</strong> <pre> <code class="language-java">package com.leftso.config.security; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; /** * 认证授权服务端 * * @author leftso * */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Value("${resource.id:spring-boot-application}") // 默认值spring-boot-application private String resourceId; @Value("${access_token.validity_period:3600}") // 默认值3600 int accessTokenValiditySeconds = 3600; @Autowired private AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(this.authenticationManager); endpoints.accessTokenConverter(accessTokenConverter()); endpoints.tokenStore(tokenStore()); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')"); oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("normal-app").authorizedGrantTypes("authorization_code", "implicit") .authorities("ROLE_CLIENT").scopes("read", "write").resourceIds(resourceId) .accessTokenValiditySeconds(accessTokenValiditySeconds).and().withClient("trusted-app") .authorizedGrantTypes("client_credentials", "password").authorities("ROLE_TRUSTED_CLIENT") .scopes("read", "write").resourceIds(resourceId).accessTokenValiditySeconds(accessTokenValiditySeconds) .secret("secret"); } /** * token converter * * @return */ @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() { /*** * 重写增强token方法,用于自定义一些token返回的信息 */ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { String userName = authentication.getUserAuthentication().getName(); User user = (User) authentication.getUserAuthentication().getPrincipal();// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration} /** 自定义一些token属性 ***/ final Map<String, Object> additionalInformation = new HashMap<>(); additionalInformation.put("userName", userName); additionalInformation.put("roles", user.getAuthorities()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication); return enhancedToken; } }; accessTokenConverter.setSigningKey("123");// 测试用,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式 return accessTokenConverter; } /** * token store * * @param accessTokenConverter * @return */ @Bean public TokenStore tokenStore() { TokenStore tokenStore = new JwtTokenStore(accessTokenConverter()); return tokenStore; } }</code></pre> SecurityConfiguration.java <pre> <code class="language-java">package com.leftso.config.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.leftso.entity.Account; import com.leftso.repository.AccountRepository; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { // 查询用户使用 @Autowired AccountRepository accountRepository; @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { // auth.inMemoryAuthentication() // .withUser("user").password("password").roles("USER") // .and() // .withUser("app_client").password("nopass").roles("USER") // .and() // .withUser("admin").password("password").roles("ADMIN"); //配置用户来源于数据库 auth.userDetailsService(userDetailsService()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and() .httpBasic().and().csrf().disable(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public UserDetailsService userDetailsService() { return new UserDetailsService() { @Override public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException { // 通过用户名获取用户信息 Account account = accountRepository.findByName(name); if (account != null) { // 创建spring security安全用户 User user = new User(account.getName(), account.getPassword(), AuthorityUtils.createAuthorityList(account.getRoles())); return user; } else { throw new UsernameNotFoundException("用户[" + name + "]不存在"); } } }; } }</code></pre> <br /> 受保护的资源服务:<br /> 核心配置:<br /> SecurityConfiguration.java <pre> <code class="language-java">package com.leftso.config; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() .anyRequest().authenticated() .and().httpBasic() .and().csrf().disable(); } } </code></pre> <br /> ResourceServerConfiguration.java <pre> <code class="language-java">package com.leftso.config; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.web.util.matcher.RequestMatcher; /** * 资源服务端 * * @author leftso * */ @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Value("${resource.id:spring-boot-application}") private String resourceId; @Override public void configure(ResourceServerSecurityConfigurer resources) { // @formatter:off resources.resourceId(resourceId); resources.tokenServices(defaultTokenServices()); // @formatter:on } @Override public void configure(HttpSecurity http) throws Exception { // @formatter:off http.requestMatcher(new OAuthRequestedMatcher()).authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll() .anyRequest().authenticated(); // @formatter:on } private static class OAuthRequestedMatcher implements RequestMatcher { public boolean matches(HttpServletRequest request) { String auth = request.getHeader("Authorization"); // Determine if the client request contained an OAuth Authorization boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer"); boolean haveAccessToken = request.getParameter("access_token") != null; return haveOauth2Token || haveAccessToken; } } // ===================================================以下代码与认证服务器一致========================================= /** * token存储,这里使用jwt方式存储 * * @param accessTokenConverter * @return */ @Bean public TokenStore tokenStore() { TokenStore tokenStore = new JwtTokenStore(accessTokenConverter()); return tokenStore; } /** * Token转换器必须与认证服务一致 * * @return */ @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() { // /*** // * 重写增强token方法,用于自定义一些token返回的信息 // */ // @Override // public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { // String userName = authentication.getUserAuthentication().getName(); // User user = (User) authentication.getUserAuthentication().getPrincipal();// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration} // /** 自定义一些token属性 ***/ // final Map<String, Object> additionalInformation = new HashMap<>(); // additionalInformation.put("userName", userName); // additionalInformation.put("roles", user.getAuthorities()); // ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); // OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication); // return enhancedToken; // } }; accessTokenConverter.setSigningKey("123");// 测试用,授权服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式 return accessTokenConverter; } /** * 创建一个默认的资源服务token * * @return */ @Bean public ResourceServerTokenServices defaultTokenServices() { final DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenEnhancer(accessTokenConverter()); defaultTokenServices.setTokenStore(tokenStore()); return defaultTokenServices; } // ===================================================以上代码与认证服务器一致========================================= } </code></pre> <br /> 项目源码下载:<br /> <a href="https://github.com/leftso/demo-spring-boot-security-oauth2" rel="external nofollow" target="_blank">https://github.com/leftso/demo-spring-boot-security-oauth2</a> <h2>四、测试上面的编码</h2> 测试过程<br /> 步骤一:打开浏览器,输入地址 <pre> <code>http://localhost:8080/oauth/authorize?client_id=normal-app&response_type=code&scope=read&redirect_uri=/resources/user</code></pre> <br /> 会提示输入用户名密码,这时候输入用户名leftso,密码111aaa将会出现以下界面<br /> <img alt="授权页面" class="img-thumbnail" src="/assist/images/blog/50f1dbe9-78f0-4085-8ade-c6840d63d192.png" /><br /> 点击Authorize将获取一个随机的code,如图:<br /> <img alt="授权码" class="img-thumbnail" src="/assist/images/blog/831a7283-9b6c-4d5d-9da3-6d3756eb069d.png" /><br /> <br />  打开工具postmain,输入以下地址获取授权token <pre> <code>localhost:8080/oauth/token?code=r8YBUL&grant_type=authorization_code&client_id=normal-app&redirect_uri=/resources/user</code></pre> <strong>注意:url中的code就是刚才浏览器获取的code值<br /> <br /> 获取的token信息如下图:<br /> <img alt="token" class="img-thumbnail" src="/assist/images/blog/7c03ded7d6524068a137ba060654f094.png" /></strong><br /> <br /> 这时候拿到token就可以访问受保护的资源信息了,如下<br />   <pre> <code>localhost:8081//resources/user</code></pre> <br /> 首先,直接访问资源,会报错401如图:<br /> <img alt="401" class="img-thumbnail" src="/assist/images/blog/c6ee8e5c-fda0-4f3f-a538-c9568136e80d.png" /><br /> <br /> 我们加上前面获取的access token再试: <pre> <code>localhost:8081//resources/user?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3ByaW5nLWJvb3QtYXBwbGljYXRpb24iXSwidXNlcl9uYW1lIjoibGVmdHNvIiwic2NvcGUiOlsicmVhZCJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn1dLCJleHAiOjE0OTEzNTkyMjksInVzZXJOYW1lIjoibGVmdHNvIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjgxNjI5NzQwLTRhZWQtNDM1Yy05MmM3LWZhOWIyODk5NmYzMiIsImNsaWVudF9pZCI6Im5vcm1hbC1hcHAifQ.YhDJkMSlyIN6uPfSFPbfRuufndvylRmuGkrdprUSJIM</code></pre> <br /> 这时候我们就能成功获取受保护的资源信息了:<br /> <img alt="resource" class="img-thumbnail" src="/assist/images/blog/31e0ae6d-f19d-41be-8174-57f5457230de.png" /><br /> <br /> 到这里spring boot整合security oauth2 的基本使用已经讲解完毕. <h2>五、扩展思维</h2> 留下一些扩展<br /> 1.认证服务的客户端信息是存放内存的,实际应用肯定是不会放内存的,考虑数据库,默认有个DataSource的方式,还有一个自己实现clientDetail接口方式<br /> 2.jwt这里测试用的最简单的对称加密,实际应用中使用的一般都是RSA非对称加密方式
  • Spring Security OAuth 2开发者指南

    本文主要翻译spring官方的基于spring security框架的oauth2开发指南,spring,oauth2,spring框架,Java编程<h2>介绍</h2> <p>这是用户指南的支持<a href="https://tools.ietf.org/html/draft-ietf-oauth-v2" rel="external nofollow" target="_blank"><code>OAuth 2.0</code></a>。对于OAuth 1.0,一切都是不同的,所以<a href="http://projects.spring.io/spring-security-oauth/docs/oauth1.html" rel="external nofollow" target="_blank">看到它的用户指南</a>。</p> <p>本用户指南分为两部分,第一部分为OAuth 2.0提供者,第二部分为OAuth 2.0客户端。对于提供商和客户端,示例代码的最佳来源是<a href="https://github.com/spring-projects/spring-security-oauth/tree/master/tests" rel="external nofollow" target="_blank">集成测试</a>和<a href="https://github.com/spring-projects/spring-security-oauth/tree/master/samples/oauth2" rel="external nofollow" target="_blank">示例应用程序</a>。</p> <h2>OAuth 2.0提供程序</h2> <p>OAuth 2.0提供者机制负责公开OAuth 2.0受保护的资源。该配置包括建立可独立或代表用户访问其受保护资源的OAuth 2.0客户端。提供者通过管理和验证用于访问受保护资源的OAuth 2.0令牌来实现。在适用的情况下,提供商还必须提供用户界面,以确认客户端可以被授权访问受保护资源(即确认页面)。</p> <h2>OAuth 2.0提供程序实现</h2> <p>OAuth 2.0中的提供者角色实际上是在授权服务和资源服务之间分割的,而有时它们位于同一个应用程序中,使用Spring Security OAuth,您可以选择在两个应用程序之间进行拆分,并且还可以共享多个资源服务授权服务。令牌的请求由Spring MVC控制器端点处理,对受保护资源的访问由标准的Spring Security请求过滤器处理。为了实现OAuth 2.0授权服务器,Spring Security过滤器链中需要以下端点:</p> <ul> <li><a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.html" rel="external nofollow" target="_blank" title="授权终点"><code>AuthorizationEndpoint</code></a>用于服务授权请求。默认网址:<code>/oauth/authorize</code>。</li> <li><a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/endpoint/TokenEndpoint.html" rel="external nofollow" target="_blank" title="令牌终点"><code>TokenEndpoint</code></a>用于服务访问令牌的请求。默认网址:<code>/oauth/token</code>。</li> </ul> <p>实施OAuth 2.0资源服务器需要以下过滤器:</p> <ul> <li>将<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilter.html" rel="external nofollow" target="_blank" title="OAuth2AuthenticationProcessingFilter"><code>OAuth2AuthenticationProcessingFilter</code></a>用于加载给定的认证访问令牌请求的认证。</li> </ul> <p>对于所有OAuth 2.0提供程序功能,使用特殊的Spring OAuth <code>@Configuration</code>适配器简化了配置。还有一个用于OAuth配置的XML命名空间,并且模式位于<a href="http://www.springframework.org/schema/security/spring-security-oauth2.xsd" rel="external nofollow" target="_blank" title="oauth2.xsd">http://www.springframework.org/schema/security/spring-security-oauth2.xsd</a>。命名空间是<code>http://www.springframework.org/schema/security/oauth2</code>。</p> <h2>授权服务器配置</h2> <p>在配置授权服务器时,必须考虑客户端用于从最终用户获取访问令牌(例如授权代码,用户凭据,刷新令牌)的授权类型。服务器的配置用于提供客户端详细信息服务和令牌服务的实现,并且启用或禁用全局机制的某些方面。但是请注意,每个客户端都可以特别配置,以便能够使用某些授权机制和访问授权。也就是因为您的提供商配置为支持“客户端凭据”授权类型,并不意味着特定客户端被授权使用该授权类型。</p> <p>该<code>@EnableAuthorizationServer</code>注释用于配置OAuth 2.0授权服务器机制,以及任何<code>@Beans</code>实现<code>AuthorizationServerConfigurer</code>(有一个方便的适配器实现)。将以下功能委派给由Spring创建并传递到以下内容的单独配置程序<code>AuthorizationServerConfigurer</code>:</p> <ul> <li><code>ClientDetailsServiceConfigurer</code>:一个定义客户端详细信息服务的配置程序。客户端的详细信息可以初始化,也可以参考现有的存储。</li> <li><code>AuthorizationServerSecurityConfigurer</code>:定义令牌端点上的安全约束。</li> <li><code>AuthorizationServerEndpointsConfigurer</code>:定义授权和令牌端点和令牌服务。</li> </ul> <p>提供商配置的一个重要方面是授权代码提供给OAuth客户端(授权代码授权)的方式。授权代码由OAuth客户端通过将最终用户指向用户可以输入其凭据的授权页面获得,导致从提供商授权服务器重定向到具有授权码的OAuth客户端。这在OAuth 2规范中有详细说明。</p> <p>在XML中,有一个<code><authorization-server/></code>元素以类似的方式用于配置OAuth 2.0授权服务器。</p> <h3>配置客户端详细信息</h3> <p>将<code>ClientDetailsServiceConfigurer</code>(从您的回调<code>AuthorizationServerConfigurer</code>)可以用来在内存或JDBC实现客户的细节服务来定义的。客户端的重要属性是</p> <ul> <li><code>clientId</code>:(必填)客户端ID。</li> <li><code>secret</code>:(可信客户端需要)客户机密码(如果有)。</li> <li><code>scope</code>:客户受限的范围。如果范围未定义或为空(默认值),客户端不受范围限制。</li> <li><code>authorizedGrantTypes</code>:授予客户端使用授权的类型。默认值为空。</li> <li><code>authorities</code>授予客户的授权机构(普通的Spring Security权威机构)。</li> </ul> <p>客户端的详细信息可以通过直接访问底层商店(例如,在数据库表中<code>JdbcClientDetailsService</code>)或通过<code>ClientDetailsManager</code>接口(这两种实现<code>ClientDetailsService</code>也实现)来更新运行的应用程序。</p> <p>注意:JDBC服务的架构未与库一起打包(因为在实践中可能需要使用太多变体),而是可以从<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">github</a>中的<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">测试代码中</a>开始。</p> <h3>管理令牌</h3> <p>该<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/AuthorizationServerTokenServices.html" rel="external nofollow" target="_blank" title="AuthorizationServerTokenServices"><code>AuthorizationServerTokenServices</code></a>接口定义了所必需的管理OAuth 2.0令牌的操作。请注意以下事项:</p> <ul> <li>当创建访问令牌时,必须存储身份验证,以便接受访问令牌的资源可以稍后引用。</li> <li>访问令牌用于加载用于授权其创建的认证。</li> </ul> <p>在创建<code>AuthorizationServerTokenServices</code>实现时,您可能需要考虑使用<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/DefaultTokenServices.html" rel="external nofollow" target="_blank" title="DefaultTokenServices"><code>DefaultTokenServices</code></a>可插入的策略来更改访问令牌的格式和存储。默认情况下,它将通过随机值创建令牌,并处理除代表它的令牌持久化之外的所有内容<code>TokenStore</code>。默认存储是<a href="http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/store/InMemoryTokenStore.html" rel="external nofollow" target="_blank" title="InMemoryTokenStore">内存中的实现</a>,但还有一些其他可用的实现。这是一个关于每一个的一些讨论的描述</p> <ul> <li> <p>默认值<code>InMemoryTokenStore</code>对于单个服务器是完全正常的(即,在发生故障的情况下,低流量和热备份备份服务器)。大多数项目可以从这里开始,也可以在开发模式下运行,以便轻松启动没有依赖关系的服务器。</p> </li> <li> <p>这<code>JdbcTokenStore</code>是同一件事的<a href="http://projects.spring.io/spring-security-oauth/docs/JdbcTokenStore" rel="external nofollow" target="_blank">JDBC版本</a>,它将令牌数据存储在关系数据库中。如果您可以在服务器之间共享数据库,则可以使用JDBC版本,如果只有一个,则扩展同一服务器的实例,或者如果有多个组件,则授权和资源服务器。要使用<code>JdbcTokenStore</code>你需要“spring-jdbc”的类路径。</p> </li> <li> <p>商店的<a href="http://projects.spring.io/spring-security-oauth/docs/%60JwtTokenStore%60" rel="external nofollow" target="_blank">JSON Web令牌(JWT)版本</a>将所有关于授权的数据编码到令牌本身(因此,根本没有后端存储是一个显着的优势)。一个缺点是您不能轻易地撤销访问令牌,因此通常被授予短期到期权,撤销在刷新令牌处理。另一个缺点是,如果您在其中存储了大量用户凭据信息,令牌可能会变得非常大。这<code>JwtTokenStore</code>不是一个真正的“商店”,因为它不会保留任何数据,但它在翻译令牌值和验证信息之间起着相同的作用<code>DefaultTokenServices</code>。</p> </li> </ul> <p>注意:JDBC服务的架构未与库一起打包(因为在实践中可能需要使用太多变体),而是可以从<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">github</a>中的<a href="https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql" rel="external nofollow" target="_blank">测试代码中</a>开始。确保<code>@EnableTransactionManagement</code>在创建令牌时,防止在竞争相同行的客户端应用程序之间发生冲突。还要注意,示例模式有明确的<code>PRIMARY KEY</code>声明 - 这些在并发环境中也是必需的。</p> <h3>JWT令牌</h3> <p>要使用JWT令牌,您需要<code>JwtTokenStore</code>在授权服务器中。资源服务器还需要能够对令牌进行解码,因此它<code>JwtTokenStore</code>具有依赖性<code>JwtAccessTokenConverter</code>,并且授权服务器和资源服务器都需要相同的实现。默认情况下,令牌被签名,资源服务器还必须能够验证签名,因此它需要与授权服务器(共享密钥或对称密钥)相同的对称(签名)密钥,或者需要公共密钥(验证者密钥),其与授权服务器中的私钥(签名密钥)匹配(公私属或非对称密钥)。公钥(如果可用)由<code>/oauth/token_key</code>端点上的授权服务器公开,默认情况下,访问规则为“denyAll()”。<code>AuthorizationServerSecurityConfigurer</code></p> <p>要使用<code>JwtTokenStore</code>你需要的“spring-security-jwt”你的类路径(你可以在与Spring OAuth相同的github仓库中找到它,但发行周期不同)。</p> <h3>赠款类型</h3> <p><code>AuthorizationEndpoint</code>可以通过以下方式配置支持的授权类型<code>AuthorizationServerEndpointsConfigurer</code>。默认情况下,所有授权类型均受支持,除了密码(有关如何切换它的详细信息,请参见下文)。以下属性会影响授权类型:</p> <ul> <li><code>authenticationManager</code>:通过注入密码授权被打开<code>AuthenticationManager</code>。</li> <li><code>userDetailsService</code>:如果您注入<code>UserDetailsService</code>或者全局配置(例如a <code>GlobalAuthenticationManagerConfigurer</code>),则刷新令牌授权将包含对用户详细信息的检查,以确保该帐户仍然活动</li> <li><code>authorizationCodeServices</code>:定义<code>AuthorizationCodeServices</code>授权代码授权的授权代码服务(实例)。</li> <li><code>implicitGrantService</code>:在批准期间管理状态。</li> <li><code>tokenGranter</code>:(<code>TokenGranter</code>完全控制授予和忽略上述其他属性)</li> </ul> <p>在XML授予类型中包含作为子元素<code>authorization-server</code>。</p> <h3>配置端点URL</h3> <p>该<code>AuthorizationServerEndpointsConfigurer</code>有一个<code>pathMapping()</code>方法。它有两个参数:</p> <ul> <li>端点的默认(框架实现)URL路径</li> <li>需要的自定义路径(以“/”开头)</li> </ul> <p>由框架提供的URL路径<code>/oauth/authorize</code>(授权端点)<code>/oauth/token</code>(令牌端点)<code>/oauth/confirm_access</code>(用户发布批准此处)<code>/oauth/error</code>(用于在授权服务器中呈现错误)<code>/oauth/check_token</code>(由资源服务器用于解码访问令牌) ,并且<code>/oauth/token_key</code>(如果使用JWT令牌,则公开用于令牌验证的公钥)。</p> <p>注意,授权端点<code>/oauth/authorize</code>(或其映射替代方案)应使用Spring Security进行保护,以便只有经过身份验证的用户才能访问。例如使用标准的Spring Security <code>WebSecurityConfigurer</code>:</p> <pre> <code class="language-java"> @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests().antMatchers("/login").permitAll().and() // default protection for all resources (including /oauth/authorize) .authorizeRequests() .anyRequest().hasRole("USER") // ... more configuration, e.g. for form login } </code></pre> <p>注意:如果您的授权服务器也是资源服务器,那么还有另一个优先级较低的安全过滤器链控制API资源。通过访问令牌来保护这些请求,您需要他们的路径<em>不</em>与主用户面临的过滤器链中的路径匹配,因此请务必包含仅在<code>WebSecurityConfigurer</code>上述中选择非API资源的请求匹配器。</p> <p>默认情况下,通过Spring OAuth在<code>@Configuration</code>使用客户机密码的HTTP Basic认证的支持中为您保护令牌端点。在XML中不是这样(因此应该明确保护)。</p> <p>在XML中,<code><authorization-server/></code>元素具有一些可以用于以类似方式更改默认端点URL的属性。该<code>/check_token</code>端点必须(与显式启用<code>check-token-enabled</code>属性)。</p> <h2>自定义UI</h2> <p>大多数授权服务器端点主要由机器使用,但是有一些资源需要一个UI,而这些资源是GET <code>/oauth/confirm_access</code>和HTML响应<code>/oauth/error</code>。它们是在框架中使用白名单实现提供的,因此授权服务器的大多数真实世界实例都希望提供自己的实例,以便他们可以控制样式和内容。所有您需要做的是<code>@RequestMappings</code>为这些端点提供一个Spring MVC控制器,并且框架默认在调度程序中占用较低的优先级。在<code>/oauth/confirm_access</code>端点中,您可以期待<code>AuthorizationRequest</code>绑定到会话中,携带所有需要用户查询的数据(默认的实现是<code>WhitelabelApprovalEndpoint</code>这样查找起始点复制)。<code>/oauth/authorize</code>您可以从该请求中获取所有数据,然后根据需要进行渲染,然后所有用户需要执行的操作都是回复有关批准或拒绝授权的信息。请求参数直接传递给您<code>UserApprovalHandler</code>,<code>AuthorizationEndpoint</code>所以您可以随便解释数据。默认<code>UserApprovalHandler</code>取决于您是否已经提供了一个<code>ApprovalStore</code>在你的<code>AuthorizationServerEndpointsConfigurer</code>(在这种情况下,它是一个<code>ApprovalStoreUserApprovalHandler</code>)或不(在这种情况下,它是一个<code>TokenStoreUserApprovalHandler</code>)。标准审批处理程序接受以下内容:默认取决于您是否已经提供了一个在你的(在这种情况下,它是一个)或不(在这种情况下,它是一个)。标准审批处理程序接受以下内容:默认取决于您是否已经提供了一个在你的(在这种情况下,它是一个)或不(在这种情况下,它是一个)。标准审批处理程序接受以下内容:</p> <ul> <li> <p><code>TokenStoreUserApprovalHandler</code>:简单的是/否决定通过<code>user_oauth_approval</code>等于“真”或“假”。</p> </li> <li> <p><code>ApprovalStoreUserApprovalHandler</code>:一组<code>scope.*</code>参数键与“*”等于所请求的范围。参数的值可以是“true”或“approved”(如果用户批准了授权),则该用户被认为已经拒绝了该范围。如果批准了至少一个范围,则赠款是成功的。</p> </li> </ul> <p>注意:不要忘记在您为用户呈现的表单中包含CSRF保护。默认情况下,Spring Security正期待一个名为“_csrf”的请求参数(它在请求属性中提供值)。有关更多信息,请参阅Spring Security用户指南,或查看whitelabel实现的指导。</p> <h3>执行SSL</h3> <p>普通HTTP对于测试是很好的,但授权服务器只能在生产中使用SSL。您可以在安全容器或代理服务器后面运行应用程序,如果正确设置代理和容器(这与OAuth2无关),则应该可以正常运行。您也可能希望使用Spring Security <code>requiresChannel()</code>限制来保护端点。对于<code>/authorize</code>端点,由您来做,作为您正常应用程序安全性的一部分。对于<code>/token</code>端点<code>AuthorizationServerEndpointsConfigurer</code>,可以使用该<code>sslOnly()</code>方法设置一个标志。在这两种情况下,安全通道设置是可选的,但是如果Spring Security在不安全的通道上检测到请求,则会导致Spring Security重定向到安全通道。</p> <h2>自定义错误处理</h2> <p>授权服务器中的错误处理使用标准Spring MVC功能,即<code>@ExceptionHandler</code>端点本身的方法。用户还可以向<code>WebResponseExceptionTranslator</code>端点自身提供这些改变响应内容的最佳方式,而不是渲染方式。在授权<code>HttpMesssageConverters</code>端点的情况下,在令牌端点和OAuth错误视图(<code>/oauth/error</code>)的情况下,异常呈现(可以添加到MVC配置中)。该白色标签错误的端点提供了HTML的响应,但用户可能需要提供自定义实现(如只需添加一个<code>@Controller</code>带<code>@RequestMapping("/oauth/error")</code>)。</p> <h2>将用户角色映射到范围</h2> <p>限制令牌范围不仅仅是分配给客户端的范围,还可以根据用户自己的权限来进行限制。如果您在其中使用<code>DefaultOAuth2RequestFactory</code>,<code>AuthorizationEndpoint</code>则可以设置一个标志<code>checkUserScopes=true</code>,以将允许的范围限制为仅与那些与用户角色匹配的范围。你也可以注入<code>OAuth2RequestFactory</code>,<code>TokenEndpoint</code>但只有工作(即密码授权),如果你也安装一个<code>TokenEndpointAuthenticationFilter</code>- 你只需要在HTTP之后添加该过滤器<code>BasicAuthenticationFilter</code>。当然,您还可以实现自己的规则,将作用域映射到角色并安装自己的版本<code>OAuth2RequestFactory</code>。将<code>AuthorizationServerEndpointsConfigurer</code>让你注入一个定制的<code>OAuth2RequestFactory</code>,所以你可以使用该功能来建立一个工厂,如果你使用<code>@EnableAuthorizationServer</code>。</p> <h2>资源服务器配置</h2> <p>资源服务器(可以与授权服务器或单独的应用程序相同)提供受OAuth2令牌保护的资源。Spring OAuth提供了实现此保护的Spring Security认证过滤器。您可以<code>@EnableResourceServer</code>在<code>@Configuration</code>类上打开它,并使用a进行配置(必要时)<code>ResourceServerConfigurer</code>。可以配置以下功能:</p> <ul> <li><code>tokenServices</code>:定义令牌服务的bean(实例<code>ResourceServerTokenServices</code>)。</li> <li><code>resourceId</code>:资源的ID(可选,但建议并由验证服务器验证,如果存在)。</li> <li>其他扩展点(例如<code>tokenExtractor</code>从传入请求中提取令牌)</li> <li>请求匹配的受保护资源(默认为全部)</li> <li>受保护资源的访问规则(默认为“已验证”)</li> <li><code>HttpSecurity</code>Spring Security中配置程序允许的受保护资源的其他自定义</li> </ul> <p>该<code>@EnableResourceServer</code>注释添加类型的过滤器<code>OAuth2AuthenticationProcessingFilter</code>自动Spring Security的过滤器链。</p> <p>在XML中有一个<code><resource-server/></code>带有<code>id</code>属性的元素- 这是一个servlet的bean ID,<code>Filter</code>然后可以手动添加到标准的Spring Security链。</p> <p>您<code>ResourceServerTokenServices</code>是与授权服务器的合同的另一半。如果资源服务器和授权服务器在同一个应用程序中,然后使用,<code>DefaultTokenServices</code>那么您不需要太费心思考,因为它实现了所有必要的接口,因此它自动一致。如果您的资源服务器是一个单独的应用程序,那么您必须确保与授权服务器的功能相匹配,并提供一个<code>ResourceServerTokenServices</code>正确的解码令牌。与授权服务器一样,您经常可以使用该<code>DefaultTokenServices</code>选项,并且选择主要通过<code>TokenStore</code>(后端存储或本地编码)来表达。<code>RemoteTokenServices</code>一个替代方案是Spring OAuth功能(不是规范的一部分),允许资源服务器通过授权服务器(<code>/oauth/check_token</code>)上的HTTP资源解码令牌。<code>RemoteTokenServices</code>如果资源服务器中没有大量的流量(每个请求都必须与授权服务器进行验证),或者如果能够缓存结果,那么它们是方便的。要使用<code>/oauth/check_token</code>端点,您需要通过更改其访问规则(默认为“denyAll()”)来公开它<code>AuthorizationServerSecurityConfigurer</code>,例如</p> <pre> <code class="language-java"> @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess( "hasAuthority('ROLE_TRUSTED_CLIENT')"); } </code></pre> <p>在这个例子中,我们配置了<code>/oauth/check_token</code>端点和<code>/oauth/token_key</code>端点(所以信任的资源可以获得JWT验证的公钥)。这两个端点受到使用客户端凭据的HTTP基本身份验证的保护。</p> <h3>配置OAuth感知表达式处理程序</h3> <p>您可能希望利用Spring Security <a href="http://docs.spring.io/spring-security/site/docs/3.2.5.RELEASE/reference/htmlsingle/#el-access" rel="external nofollow" target="_blank" title="表达式访问控制">基于表达式的访问控制</a>。表达式处理程序将默认在<code>@EnableResourceServer</code>安装程序中注册。这些表达式包括<em>#oauth2.clientHasRole</em>,<em>#oauth2.clientHasAnyRole</em>和<em>#oath2.denyClient</em>,可用于根据oauth客户端的角色提供访问(请参阅<code>OAuth2SecurityExpressionMethods</code>全面的列表)。在XML中,您可以<code>expression-handler</code>使用常规<code><http/></code>安全配置的元素注册一个oauth感知表达式处理程序。</p> <h2>OAuth 2.0客户端</h2> <p>OAuth 2.0客户端机制负责访问其他服务器的OAuth 2.0保护资源。该配置包括建立用户可能访问的相关受保护资源。客户端还可能需要提供用于存储用户的授权码和访问令牌的机制。</p> <h3>受保护的资源配置</h3> <p>受保护的资源(或“远程资源”)可以使用类型的bean定义来定义<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java" rel="external nofollow" target="_blank"><code>OAuth2ProtectedResourceDetails</code></a>。受保护的资源具有以下属性:</p> <ul> <li><code>id</code>:资源的id。该id仅由客户端用于查找资源; 它在OAuth协议中从未使用过。它也被用作bean的id。</li> <li><code>clientId</code>:OAuth客户端ID。这是OAuth提供商识别您的客户端的ID。</li> <li><code>clientSecret</code>:与资源相关的秘密。默认情况下,没有密码为空。</li> <li><code>accessTokenUri</code>:提供访问令牌的提供者OAuth端点的URI。</li> <li><code>scope</code>:逗号分隔的字符串列表,指定对资源的访问范围。默认情况下,不指定范围。</li> <li><code>clientAuthenticationScheme</code>:您的客户端用于向访问令牌端点进行身份验证的方案。建议的值:“http_basic”和“form”。默认值为“http_basic”。请参阅OAuth 2规范的第2.1节。</li> </ul> <p>不同的授权类型具有不同的具体实现<code>OAuth2ProtectedResourceDetails</code>(例如<code>ClientCredentialsResource</code>,对于“client_credentials”授权类型)。对于需要用户授权的授权类型,还有一个其他属性:</p> <ul> <li><code>userAuthorizationUri</code>:如果用户需要授权访问资源,则用户将被重定向到的uri。请注意,这并不总是需要,具体取决于支持哪个OAuth 2配置文件。</li> </ul> <p>在XML中有一个<code><resource/></code>可以用来创建类型的bean的元素<code>OAuth2ProtectedResourceDetails</code>。它具有匹配上述所有属性的属性。</p> <h3>客户端配置</h3> <p>对于OAuth 2.0客户端,使用简化配置<code>@EnableOAuth2Client</code>。这有两件事情:</p> <ul> <li> <p>创建一个过滤器bean(带有ID <code>oauth2ClientContextFilter</code>)来存储当前的请求和上下文。在需要在请求期间进行身份验证的情况下,管理重定向到和从OAuth认证uri。</p> </li> <li> <p><code>AccessTokenRequest</code>在请求范围中创建一个类型的bean 。授权代码(或隐式)授权客户端可以使用这种方式来保持与个别用户的状态相关。</p> </li> </ul> <p>过滤器必须连接到应用程序中(例如,使用 同一名称的Servlet初始化程序或<code>web.xml</code>配置<code>DelegatingFilterProxy</code>)。</p> <p>本<code>AccessTokenRequest</code>可以在使用 <code>OAuth2RestTemplate</code>这样的:</p> <pre> <code class="language-java">@Autowired private OAuth2ClientContext oauth2Context; @Bean public OAuth2RestTemplate sparklrRestTemplate() { return new OAuth2RestTemplate(sparklr(), oauth2Context); } </code></pre> <p>在会话范围中放置OAuth2ClientContext(为您),以保持不同用户的状态分开。没有了,您将不得不自己在服务器上管理等效的数据结构,将传入的请求映射到用户,并将每个用户与单独的实例相关联<code>OAuth2ClientContext</code>。</p> <p>在XML中有一个<code><client/></code>带有<code>id</code>属性的元素- 这是一个servlet的bean id,<code>Filter</code>在这种<code>@Configuration</code>情况下必须映射为一个<code>DelegatingFilterProxy</code>(具有相同名称)。</p> <h3>访问受保护的资源</h3> <p>一旦您提供了资源的所有配置,您现在可以访问这些资源。用于访问这些资源的建议的方法是通过使用<a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html" rel="external nofollow" target="_blank" title="RestTemplate">所述<code>RestTemplate</code>在弹簧3引入</a>。Spring Security的OAuth提供<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java" rel="external nofollow" target="_blank">了</a>只需要提供一个实例的<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java" rel="external nofollow" target="_blank">RestTemplate的扩展</a><a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java" rel="external nofollow" target="_blank"><code>OAuth2ProtectedResourceDetails</code></a>。要使用用户令牌(授权代码授权),您应该考虑使用创建一些请求和会话作用域上下文对象的<code>@EnableOAuth2Client</code>配置(或XML等效项<code><oauth:rest-template/></code>),以便不同用户的请求在运行时不会相冲突。</p> <p>作为一般规则,Web应用程序不应使用密码授权,因此<code>ResourceOwnerPasswordResourceDetails</code>如果可以支持,请避免使用<code>AuthorizationCodeResourceDetails</code>。如果您非常需要从Java客户端工作的密码授权,则使用相同的机制来配置您的凭据,并将凭据<code>OAuth2RestTemplate</code>添加到<code>AccessTokenRequest</code>(这是一个<code>Map</code>短暂的),而不是<code>ResourceOwnerPasswordResourceDetails</code>(在所有访问令牌之间共享)。</p> <h3>在客户端中持久化令牌</h3> <p>客户端并不<em>需要</em>坚持令牌,但它可以很好的为不要求用户每次在客户端应用程序重新启动时批准新的代金券授予。该<a href="http://projects.spring.io/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientTokenServices.java" rel="external nofollow" target="_blank"><code>ClientTokenServices</code></a>接口定义了所必需的持续的OAuth为特定用户2.0的令牌的动作。提供了一个JDBC实现,但如果您希望实现自己的服务来将持久性数据库中的访问令牌和关联的身份验证实例存储起来,那么您可以使用。如果要使用此功能,您需要提供一个专门配置<code>TokenProvider</code>的<code>OAuth2RestTemplate</code>如</p> <pre> <code class="language-java">@Bean @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) public OAuth2RestOperations restTemplate() { OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest)); AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider())); provider.setClientTokenServices(clientTokenServices()); return template; } </code></pre> <h2>外部OAuth2提供商客户端的定制</h2> <p>一些外部OAuth2提供者(例如<a href="https://developers.facebook.com/docs/authentication" rel="external nofollow" target="_blank" title="Facebook">Facebook</a>)不能正确地实现规范,或者他们只是坚持使用旧版本的规范,而不是Spring Security OAuth。要在客户端应用程序中使用这些提供程序,您可能需要调整客户端基础架构的各个部分。</p> <p>以Facebook为例,应用程序中有一个Facebook功能<code>tonr2</code>(您需要更改配置以添加您自己的,有效的客户端ID和密码 - 它们很容易在Facebook网站上生成)。</p> <p>Facebook令牌响应在令牌的到期时间(它们使用<code>expires</code>而不是<code>expires_in</code>)中也包含不符合规定的JSON条目,因此,如果要在应用程序中使用到期时间,则必须使用自定义手动解码<code>OAuth2SerializationService</code>。</p>