6つの方法:JavaでRestサービスのセキュリティを追加する方法

この記事では、JavaのRESTサービスにセキュリティを追加するいくつかの方法、または6を説明しようとします。



私たちのチームは、残りのサービスにセキュリティを追加するためのすべての可能な方法を見つけることを任されました。 長所と短所を分析し、プロジェクトに最適なものを選択します。 Googleでそのような記事を探し始めたとき、適切なものは見つかりませんでしたが、断片しかなく、この情報を少しずつ収集する必要がありました。 したがって、この記事は、バックエンドを作成する他のJava開発者にとっても役立つと思います。 私はこれらの方法のいずれかが良いか悪いかを主張するつもりはありません。それはすべてタスクと特定のプロジェクトに依存します。 したがって、6つの方法のうち、プロジェクトに最も適した方法はあなた次第です。 各アプローチの原理を説明し、JavaとSpring Securityを使用した簡単な例を示します。



方法1 :基本認証



基本認証 -ユーザーまたは休憩クライアントは、休憩サービスにアクセスするためにユーザー名とパスワードを指定します。 ログインとパスワードは、単純なBase64でエンコードされたプレーンテキストとしてネットワーク経由で送信され、どのユーザーでも簡単にデコードできます。 この方法を使用する場合、データ転送にはhttpsプロトコルを使用する必要があります。



構成は非常に簡単で、Spring Securityのsecurity.xmlのようになります。



<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <http auto-config="true"> <intercept-url pattern="/rest/**" access="ROLE_USER" /> <logout/> </http> <authentication-manager> <authentication-provider> <user-service > <user name="user" password="pass" authorities="ROLE_USER" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
      
      





これがレストコントローラーです。



 @RequestMapping("/rest/api") @RestController public class RestController { @RequestMapping public Object getInfo() { return //some response MyClass; } }
      
      





そして最後に、RestTemplate sprinに基づいたRESTクライアント。 ヘッダーに、「Basic space」という単語を追加し、その後にスペースなしのユーザー名とパスワードをコロンで区切ってエンコードし、Base64をエンコードします。



  RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/rest/api"; HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Basic QWxhZGRpbupvcRVuIHNlc2FtZQ=="); //here is some login and pass like this login:pass HttpEntity<String> request = new HttpEntity<String>(headers); MyClass myclass = restTemplate.exchange(url, HttpMethod.GET, request, MyClass.class).getBody();
      
      





方法2 :ダイジェスト認証



ダイジェスト認証 -これは最初の方法とほとんど同じです。ログインとパスワードのみが暗号化された形式で送信され、プレーンテキストでは送信されません。 ログインとパスワードはMD5アルゴリズムで暗号化されており、解読するのは非常に困難です。 このアプローチを使用すると、安全でないhttp接続を使用でき、パスワードが攻撃者に傍受されることを恐れません。 サーバー側の実装は同じままです。 暗号化された形式でパスワードを送信できるように、クライアントを少し変更する必要があります。 ここで、http Apacheクライアントが助けになります。

コピーペーストを行わないために、 このようなクライアントの実装を含むGithubのプロジェクトへのリンクを提供します



方法3 :トークン認証



この方法の本質は、資格情報を使用するユーザーがアプリケーションにログインし、残りのサービスにアクセスするためのトークンを受け取ることです。 トークンを発行するサービスへのアクセスは、必ずhttps接続を介して行う必要があり、残りのサービスへのアクセスは通常のhttpを介して行うことができます。 トークンには、ログイン、パスワードが含まれている必要があります。また、有効期限とユーザーロール、およびアプリケーションに必要な情報を含めることができます。 トークンの準備が完了し、たとえば、すべてのパラメーターがコロンまたはその他の便利な記号で区切られているか、jsonまたはxmlオブジェクトとしてシリアル化された後、ユーザーに送信する前に暗号化する必要があります。 このトークンを復号化する方法を知っているのは、レストサービスのみであることに注意してください。 トークンが残りのサービスに到着すると、トークンを復号化し、認証に必要なすべてのデータを受け取ります。残りのクライアントを認証する必要がある場合。 実装は、前の2つとは根本的に異なります。



security.xmlは次のようになります。



 <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <beans:bean id="restAuthenticationEntryPoint" class="com.example.rest.security.RestAuthenticationEntryPoint"/> <http pattern="/rest/**" entry-point-ref="restAuthenticationEntryPoint" use-expressions="true" auto-config="false" create-session="stateless" > <custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" /> <intercept-url pattern="/rest/**" access="isAuthenticated()" /> <logout /> </http> <beans:bean class="com.example.rest.security.CustomTokenAuthenticationFilter" id="authenticationTokenProcessingFilter"> <beans:constructor-arg type="java.lang.String" value="/rest/**"/> <beans:constructor-arg type="org.springframework.security.authentication.AuthenticationManager" ref="authManager"> </beans:constructor-arg> </beans:bean> <http auto-config="true"> <intercept-url pattern="/token/**" access="ROLE_USER" /> </http> <authentication-manager alias="authManager" erase-credentials="false"> <authentication-provider> <user-service > <user name="user" password="pass" authorities="ROLE_USER" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
      
      





RestAuthenticationEntryPoint Beanは次のようになります。



 public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException, ServletException { response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" ); } }
      
      





CustomTokenAuthenticationFilterをフィルターします。これにより、トークンの有効性、権利などがチェックされます。 そして最終的に、このクライアントが私たちの休息サービスを使用できるかどうかを決定するために、このようになりますが、別の方法で実装することができます。



 public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private AuthenticationManager authenticationManager; @Autowired private CryptService cryptService; //service which can decrypt token public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) { super(defaultFilterProcessesUrl); this.authenticationManager = authenticationManager; super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl)); setAuthenticationManager(new NoOpAuthenticationManager()); setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler()); } public final String HEADER_SECURITY_TOKEN = "My-Rest-Token"; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String token = request.getHeader(HEADER_SECURITY_TOKEN); Authentication userAuthenticationToken = parseToken(token); if (userAuthenticationToken == null) { throw new AuthenticationServiceException("here we throw some exception or text"); } return userAuthenticationToken; } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { super.successfulAuthentication(request, response, chain, authResult); chain.doFilter(request, response); } // This method makes some validation depend on your application logic private Authentication parseToken(String tokenString) { try { String encryptedToken = cryptService.decrypt(tokenString); Token token = new ObjectMapper().readValue(encryptedToken, Token.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(token.getUsername(), token.getPassword())); } catch (Exception e) { return null; } return null; } }
      
      





最後に何がありますか。 ユーザーがアプリケーションにログインすると、暗号化されたトークンを受け取ります。暗号化されたトークンは、Spring RestTemplateを使用するか、カスタムヘッダーMy-Rest-Tokenなど、ヘッダーに追加する別のクライアントを使用できます。 サーバー側では、フィルターはこのヘッダーから値を受け取り、トークンを解読し、それを解析するか、コンポーネントに解析して、クライアントにアクセスを許可するかどうかを決定します。



方法4 :デジタル署名(公開/秘密キーのペア)



このアプローチの後ろの考えは公開鍵暗号システムを使用することです。 一番下の行は、誰でも残りのサービスに頼ってランダムな文字セットを取得できるか、サーバーから暗号化された応答を取得し、秘密鍵の所有者のみがそれを復号化できることです。

そして順番に。

  1. 新しいユーザーがサーバーに登録されると、このユーザーに対して公開鍵と非公開鍵のペアが生成されます
  2. プライベートはユーザーに送信され、ユーザーのみがメッセージを復号化できます(キーは安全なチャネルを介して送信されるため、誰も傍受することはできません)
  3. 残りの要求ごとに、クライアントはユーザー名を送信します。これにより、サービスは目的の公開鍵でメッセージを暗号化できます
  4. サービスはメッセージを暗号化して送信します
  5. クライアントはそれを受け入れ、そのキーで復号化します


このアプローチを実装するには、2つのフィルターを作成する必要があります。1つはサーバー側、もう1つはクライアント側です。 レストサーバーのサーバー側のフィルターは、要求を行ったクライアントのキーを使用してrisponを暗号化します。 レストクライアントのフィルターは、プライベートキーを使用してレストサービスからリスポンを解読します。



forgeなどのjavascriptライブラリを使用してクライアント側でキーペアを生成する場合、このアプローチはさらに安全になります。 このアプローチにより、プライベートキーをネットワーク経由で送信するのではなく、クライアント側ですぐに生成できるため、このキーが危険にさらされるリスクが大幅に削減されます。 公開キーは、メッセージ暗号化でさらに使用するためにサーバーに送信されます。 送信キーは安全ではないかもしれません。公開鍵が傍受されても心配する必要がないためです(詳細については、公開鍵暗号システムの上のリンクを参照してください)。



方法5 :証明書認証



クライアントが要求時に必要な証明書を提供しない場合、サーバーから応答を受信しないように、または証明書が欠落しているか適切でないという応答を受信するようにサーバーを構成できます。 証明書の詳細については、 こちらをご覧ください 。 証明書には2つのタイプがあります。



以下に、keytoolユーティリティを使用して自己署名証明書を作成する方法のいくつかの手順を示します。



クライアントキーとサーバーキーを生成する

keytool -genkey -keystore keystore_client -alias clientKey

keytool -genkey -keystore keystore_server -alias serverKey



クライアント証明書とサーバー証明書を生成する

keytool -export -alias clientKey -rfc -keystore keystore_client> client.cert

keytool -export -alias serverKey -rfc -keystore keystore_server> server.cert



証明書を対応するトラストストアにインポートする

keytool -import -alias clientCert -file client.cert -keystore truststore_server

keytool -import -alias serverCert -file server.cert -keystore truststore_client



これで、受信した証明書をサーバー構成に追加する必要があります。 この場合、Tomcatが使用されます。

 <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"       maxThreads="150" SSLEnabled="true" scheme="https" secure="true"       keystoreFile="${catalina.home}/conf/cert/keystore_server" keystorePass="changeit"       truststoreFile="${catalina.home}/conf/cert/truststore_server" truststorePass="changeit"       clientAuth="true" sslProtocol="TLS" />
      
      





以下は、HTTP Apacheクライアントを使用するRESTクライアントです。HTTPApacheクライアントは、証明書をRESTサービスに提供し、サーバーから応答を受信するために必要なすべての「ハンドシェイク」を実行できます。



 public class CertificateAuthenticationServiceImpl implements CertificateAuthenticationService { private static final String keyStorePass = "changeit"; private static final String trustedStorePass = "changeit"; private static final File keyStore = new File(new CertificateAuthenticationServiceImpl().getClass().getResource("/authCertificate/keystore_client").getPath()); private static final File trustedStore = new File(new CertificateAuthenticationServiceImpl().getClass().getResource("/authCertificate/truststore_client").getPath()); private static final String certificateType = "jks"; public String httpGet(URL url) { String resp = null; try { final HttpParams httpParams = new BasicHttpParams(); final KeyStore keystore = KeyStore.getInstance(certificateType); keystore.load(new FileInputStream(keyStore), keyStorePass.toCharArray()); final KeyStore truststore = KeyStore.getInstance(certificateType); truststore.load(new FileInputStream(trustedStore), trustedStorePass.toCharArray()); final SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme(url.toURI().getScheme(), new SSLSocketFactory(keystore, keyStorePass, truststore), url.getPort())); final DefaultHttpClient httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(httpParams, schemeRegistry), httpParams); try { HttpGet httpget = new HttpGet(url.toString()); CloseableHttpResponse response = httpClient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { resp = EntityUtils.toString(entity); } EntityUtils.consume(entity); } finally { response.close(); } } finally { httpClient.close(); } } catch (Exception e) { throw new RuntimeException(e); } return resp; } }
      
      





方法6 :OAuth2認証



まあ、初心者のために、私は最も理解しにくいメソッドを実装しました。 ただし、非常に柔軟性が高く、大規模なポータルに適しています。 繰り返しますが、OAuthとは何か、どのように機能するかを読むためにコピー&ペーストはしません。

SpringセキュリティはOAuthTemplateクラスを提供します。これにより、作業がはるかに簡単になります。

OAuthの実装を実装するためのすべてのアイデアは、この素晴らしい記事から得たもので、ダウンロード可能なドラフトもあります。



おわりに



さて、私は全体像を少し明確にし、あなた自身のプロジェクトであなたを助けることができたと思います。 もちろん、これらはあなたの休息サービスを保護するすべての方法ではありませんが、あらゆる好みのためのソリューションがあります。 実装例は一般的なソリューションではありませんが、全体像を理解するためにのみ提供されています。 プロジェクトのニーズに応じて、独自の実装を作成できます。



プロジェクトのセキュリティを選択する前に、必要なものを明確に決定し、長所と短所を比較検討してください。 これがプロジェクトで必要でない場合、システムを複雑にしないでください。



アプリケーションが安全で信頼できるものになることを願っています。



All Articles