`
kobe学java
  • 浏览: 249945 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

Spring Security 3 与 CAS单点登录配置-Server

 
阅读更多

转载:http://www.hxdw.com/bbs/post/view?bid=60&id=144184&sty=3&keywords=Spring+Security+3

一、环境说明: 
Tomcat 6.0.20 
JDK 1.6.x 
Spring 3.0.0.RELEASE 
Spring Security 3.0.0.RELEASE 
CAS cas-server-3.4.2 
cas-client-3.1.10 
下载地址:http://www.jasig.org/cas/download 

配置环境变量 
JAVA_HOME : C:\Java\jdk1.6.0_11 
CATALINA_HOME : C:\Tomcat 6.0 
Path :C:\Java\jdk1.6.0_11\bin; 

二、开发步骤 
1.制作SSL证书 
2.配置CAS Server 
3.配置CAS Client 
4.安全框架合并 
5.简单测试 

三、开始 
证书制作,请参考 
http://linliangyi2007.javaeye.com/blog/165304 
说明 

*附件是制作证书的批处理程序,可以按照提示完成证书制作。 
*局域网用户,不要使用localhost做这个域名,因为你的证书设置了localhost,其他客户端要从你的域名下证书,而localhost又是客户端的域(可以查看%SystemRoot%\system32\drivers\etc\hosts) 
所以CAS服务器,修改%SystemRoot%\system32\drivers\etc\hosts文件 
增加:127.0.0.1 cas.com 
Java代码 
127.0.0.1 localhost 
127.0.0.1 cas.boc.com 

127.0.0.1 localhost
127.0.0.1 cas.boc.com

而CAS客户端,也修改hosts文件,但要域要对应服务器端的IP 
Java代码 
127.0.0.1 localhost 
192.168.0.2 cas.boc.com 

127.0.0.1 localhost
192.168.0.2 cas.boc.com

配置Tomcat的Server.xml文件 
Xml代码 
<Connector protocol="org.apache.coyote.http11.Http11Protocol" 
port="8443" minSpareThreads="5" maxSpareThreads="75" 
enableLookups="true" disableUploadTimeout="true" 
acceptCount="100" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" 
keystoreFile="%USERPROFILE%/.keystore" 
keystorePass="changeit" clientAuth="false" 
sslProtocol="TLS"/> 

  <Connector protocol="org.apache.coyote.http11.Http11Protocol"
port="8443" minSpareThreads="5" maxSpareThreads="75"
       enableLookups="true" disableUploadTimeout="true" 
       acceptCount="100" maxThreads="200" scheme="https" secure="true" SSLEnabled="true"
       keystoreFile="%USERPROFILE%/.keystore"
       keystorePass="changeit" clientAuth="false"
       sslProtocol="TLS"/>

开始配置CAS Server 
解压缩cas-server-3.4.2-release.zip文件 
eclipse导入modules文件夹内的cas-server-webapp-3.4.2.war 
并改名为casServer 
导入后需要配置casServer工程的Java Build Path 


这时运行会报一个错误,找不到AutowiringSchedulerFactoryBean类 
AutowiringSchedulerFactoryBean类是一个Quartz的一个调度程序,定时监控CAS Server状态 
删除WEB-INF/spring-configuration/applicationContext.xml中的配置 
或者 
AutowiringSchedulerFactoryBean添加到org.jasig.cas.util包下 
(AutowiringSchedulerFactoryBean类在最后附件中可以下载) 

如果你对CAS单点登录的工作不是很熟悉,请查看 
打开/WEB-INF/deployerConfigContext.xml 
Xml代码 
<bean id="authenticationManager" 
class="org.jasig.cas.authentication.AuthenticationManagerImpl"> 
<!-- 
| This is the List of CredentialToPrincipalResolvers that identify what Principal is trying to authenticate. 
| The AuthenticationManagerImpl considers them in order, finding a CredentialToPrincipalResolver which 
| supports the presented credentials. 

| AuthenticationManagerImpl uses these resolvers for two purposes. First, it uses them to identify the Principal 
| attempting to authenticate to CAS /login . In the default configuration, it is the DefaultCredentialsToPrincipalResolver 
| that fills this role. If you are using some other kind of credentials than UsernamePasswordCredentials, you will need to replace 
| DefaultCredentialsToPrincipalResolver with a CredentialsToPrincipalResolver that supports the credentials you are 
| using. 

| Second, AuthenticationManagerImpl uses these resolvers to identify a service requesting a proxy granting ticket. 
| In the default configuration, it is the HttpBasedServiceCredentialsToPrincipalResolver that serves this purpose. 
| You will need to change this list if you are identifying services by something more or other than their callback URL. 
+--> 
<property name="credentialsToPrincipalResolvers"> 
<list> 
<!-- 
| UsernamePasswordCredentialsToPrincipalResolver supports the UsernamePasswordCredentials that we use for /login 
| by default and produces SimplePrincipal instances conveying the username from the credentials. 

| If you've changed your LoginFormAction to use credentials other than UsernamePasswordCredentials then you will also 
| need to change this bean declaration (or add additional declarations) to declare a CredentialsToPrincipalResolver that supports the 
| Credentials you are using. 
+--> 
<bean 
class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" /> 
<!-- 
| HttpBasedServiceCredentialsToPrincipalResolver supports HttpBasedCredentials. It supports the CAS 2.0 approach of 
| authenticating services by SSL callback, extracting the callback URL from the Credentials and representing it as a 
| SimpleService identified by that callback URL. 

| If you are representing services by something more or other than an HTTPS URL whereat they are able to 
| receive a proxy callback, you will need to change this bean declaration (or add additional declarations). 
+--> 
<bean 
class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" /> 
</list> 
</property> 

<!-- 
| Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, 
| AuthenticationHandlers actually authenticate credentials. Here we declare the AuthenticationHandlers that 
| authenticate the Principals that the CredentialsToPrincipalResolvers identified. CAS will try these handlers in turn 
| until it finds one that both supports the Credentials presented and succeeds in authenticating. 
+--> 
<property name="authenticationHandlers"> 
<list> 
<!-- 
| This is the authentication handler that authenticates services by means of callback via SSL, thereby validating 
| a server side SSL certificate. 
+--> 
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" 
p:httpClient-ref="httpClient" /> 
<!-- 
| This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS 
| into production. The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials 
| where the username equals the password. You will need to replace this with an AuthenticationHandler that implements your 
| local authentication strategy. You might accomplish this by coding a new such handler and declaring 
| edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules. 
+--> 
<!-- <bean 
class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> 
<bean class="com.cas.service.CasUsernamePasswordAuthenticationHandler"> 
<property name="userDao" ref="userDao" /> 
</bean> --> 
<ref bean="casAuthenticationHandler"/> 
</list> 
</property> 
</bean> 

<bean id="authenticationManager"
    class="org.jasig.cas.authentication.AuthenticationManagerImpl">
    <!--
      | This is the List of CredentialToPrincipalResolvers that identify what Principal is trying to authenticate.
      | The AuthenticationManagerImpl considers them in order, finding a CredentialToPrincipalResolver which 
      | supports the presented credentials.
      |
      | AuthenticationManagerImpl uses these resolvers for two purposes. First, it uses them to identify the Principal
      | attempting to authenticate to CAS /login . In the default configuration, it is the DefaultCredentialsToPrincipalResolver
      | that fills this role. If you are using some other kind of credentials than UsernamePasswordCredentials, you will need to replace
      | DefaultCredentialsToPrincipalResolver with a CredentialsToPrincipalResolver that supports the credentials you are
      | using.
      |
      | Second, AuthenticationManagerImpl uses these resolvers to identify a service requesting a proxy granting ticket. 
      | In the default configuration, it is the HttpBasedServiceCredentialsToPrincipalResolver that serves this purpose. 
      | You will need to change this list if you are identifying services by something more or other than their callback URL.
      +-->
    <property name="credentialsToPrincipalResolvers">
      <list>
        <!--
          | UsernamePasswordCredentialsToPrincipalResolver supports the UsernamePasswordCredentials that we use for /login 
          | by default and produces SimplePrincipal instances conveying the username from the credentials.
          | 
          | If you've changed your LoginFormAction to use credentials other than UsernamePasswordCredentials then you will also
          | need to change this bean declaration (or add additional declarations) to declare a CredentialsToPrincipalResolver that supports the
          | Credentials you are using.
          +-->
        <bean
          class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />
        <!--
          | HttpBasedServiceCredentialsToPrincipalResolver supports HttpBasedCredentials. It supports the CAS 2.0 approach of
          | authenticating services by SSL callback, extracting the callback URL from the Credentials and representing it as a
          | SimpleService identified by that callback URL.
          |
          | If you are representing services by something more or other than an HTTPS URL whereat they are able to
          | receive a proxy callback, you will need to change this bean declaration (or add additional declarations).
          +-->
        <bean
          class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
      </list>
    </property>

    <!--
      | Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, 
      | AuthenticationHandlers actually authenticate credentials. Here we declare the AuthenticationHandlers that
      | authenticate the Principals that the CredentialsToPrincipalResolvers identified. CAS will try these handlers in turn
      | until it finds one that both supports the Credentials presented and succeeds in authenticating.
      +-->
    <property name="authenticationHandlers">
      <list>
        <!--
          | This is the authentication handler that authenticates services by means of callback via SSL, thereby validating
          | a server side SSL certificate.
          +-->
        <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
          p:httpClient-ref="httpClient" />
        <!--
          | This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS 
          | into production. The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials
          | where the username equals the password. You will need to replace this with an AuthenticationHandler that implements your
          | local authentication strategy. You might accomplish this by coding a new such handler and declaring
          | edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules.
          +-->
        <!-- <bean
          class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> 
          <bean class="com.cas.service.CasUsernamePasswordAuthenticationHandler">
            <property name="userDao" ref="userDao" />
          </bean> -->
          <ref bean="casAuthenticationHandler"/>
      </list>
    </property>
  </bean>

其中authenticationHandlers属性是个关键,它为CAS服务器提供用户认证的依据 
(它只负责用户的认证,不负责受权服务。也就是说,CAS服务器不关心用户角色) 
应该注意的是,authenticationHandlers属性的值是实现了AuthenticationHandler接口类的List,多个应用提供用户账号和密码的数据源,CAS验证时,会在这个List的验证接口中逐一遍历。 
可能会有疑问,当有两个应用A和B,分别有账号abc/123和abc/abc 
当B系统登陆时,使用了A的账户和密码(即同账户不同密码问题),这时,CAS将提供一个有效验证,返回给B应用。这不会乱套? 
其实,当你配置了Client端时,这个问题就能被解释了,其实,CAS为Client提供一个ticket后,Client需要用登陆的账号和密码再验证一次,并且取得用户的角色信息。如果没有权限,则提示401,账号被锁定 


言归正传,这里的casAuthenticationHandler是一个类实现authenticationHandlers接口的类,但你可以继承一个抽象类AbstractUsernamePasswordAuthenticationHandler去实现。 
因为我的cas使用了iBatis.所以直接注入UserDao进行登录有效性验证 
Java代码 
package com.cas.service; 

import org.jasig.cas.authentication.handler.AuthenticationException; 
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; 
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; 

import com.cas.dao.UserDao; 
import com.cas.model.User; 

public class CasUsernamePasswordAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { 

/** 
* 用户信息操作层 
*/ 
private UserDao userDao; 

@Override 
protected boolean authenticateUsernamePasswordInternal( 
UsernamePasswordCredentials credentials) 
throws AuthenticationException { 
User user = userDao.findUserByName(credentials.getUsername()); 

if (user == null) {return false;} 

if (user.getPassword().equals(credentials.getPassword())) { 
return true; 


return false; 


public void setUserDao(UserDao userDao) { 
this.userDao = userDao; 



package com.cas.service;

import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;

import com.cas.dao.UserDao;
import com.cas.model.User;

public class CasUsernamePasswordAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {

  /**
   * 用户信息操作层
   */
  private UserDao userDao;
  
  @Override
  protected boolean authenticateUsernamePasswordInternal(
      UsernamePasswordCredentials credentials)
      throws AuthenticationException {
    User user = userDao.findUserByName(credentials.getUsername());
    
    if (user == null) {return false;}
    
    if (user.getPassword().equals(credentials.getPassword())) {
      return true;
    }
    
    return false;
  }

  public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
  }
}

接下来修改deployerConfigContext.xml文件的 
Xml代码 
<sec:user-service id="userDetailsService"> 
<sec:user name="user01" password="user01" authorities="ROLE_USER" /> 
<sec:user name="admin" password="admin" authorities="ROLE_ADMIN" /> 
<sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused" authorities="ROLE_ADMIN" /> 
</sec:user-service> 

<sec:user-service id="userDetailsService">
  <sec:user name="user01" password="user01" authorities="ROLE_USER" />
  <sec:user name="admin" password="admin" authorities="ROLE_ADMIN" />
<sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused" authorities="ROLE_ADMIN" />
</sec:user-service>

这是登录cas管理页面的账号密码(这是一个简单的配置,也可以实现UserDetailsService接口,进行配置。这跟Spring Security的用户登录验证接口其实是一个) 

配置cas.properties文件 
Xml代码 
#cas.securityContext.serviceProperties.service=http://localhost:8080/casServer/services/j_acegi_cas_security_check 
cas.securityContext.serviceProperties.service=http://cas.boc.com:8080/casServer/services/j_acegi_cas_security_check 
# Names of roles allowed to access the CAS service manager 
cas.securityContext.serviceProperties.adminRoles=ROLE_ADMIN 
cas.securityContext.casProcessingFilterEntryPoint.loginUrl=http://cas.boc.com:8080/casServer/login 
cas.securityContext.ticketValidator.casServerUrlPrefix=http://cas.boc.com:8080/casServer 


cas.themeResolver.defaultThemeName=default 
cas.viewResolver.basename=default_views 

host.name=casServer 

#database.hibernate.dialect=org.hibernate.dialect.OracleDialect 
#database.hibernate.dialect=org.hibernate.dialect.MySQLDialect 
database.hibernate.dialect=org.hibernate.dialect.HSQLDialect 

#cas.securityContext.serviceProperties.service=http://localhost:8080/casServer/services/j_acegi_cas_security_check
cas.securityContext.serviceProperties.service=http://cas.boc.com:8080/casServer/services/j_acegi_cas_security_check
# Names of roles allowed to access the CAS service manager
cas.securityContext.serviceProperties.adminRoles=ROLE_ADMIN
cas.securityContext.casProcessingFilterEntryPoint.loginUrl=http://cas.boc.com:8080/casServer/login
cas.securityContext.ticketValidator.casServerUrlPrefix=http://cas.boc.com:8080/casServer

cas.themeResolver.defaultThemeName=default
cas.viewResolver.basename=default_views

host.name=casServer

#database.hibernate.dialect=org.hibernate.dialect.OracleDialect
#database.hibernate.dialect=org.hibernate.dialect.MySQLDialect
database.hibernate.dialect=org.hibernate.dialect.HSQLDialect

启动Cas Server 
如果一切正常,能看到服务启动 
因为配置了 
Xml代码 
<bean id="scheduler" class="org.jasig.cas.util.AutowiringSchedulerFactoryBean"/> 

<bean id="scheduler" class="org.jasig.cas.util.AutowiringSchedulerFactoryBean"/>

每隔一段时间,控制台将有当前服务信息提示。 

到目前为止,CAS服务器的雏形配置完成。这是一个基本的配置,我们查看cas-servlet.xml 
文件 
Xml代码 
<bean 
id="handlerMappingC" 
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 
<property 
name="mappings"> 
<props> 
<prop 
key="/logout"> 
logoutController 
</prop> 
<prop 
key="/serviceValidate"> 
serviceValidateController 
</prop> 
<prop 
key="/validate"> 
legacyValidateController 
</prop> 
<prop 
key="/proxy"> 
proxyController 
</prop> 
<prop 
key="/proxyValidate"> 
proxyValidateController 
</prop> 
<prop 
key="/samlValidate"> 
samlValidateController 
</prop> 

<prop 
key="/services/add.html"> 
addRegisteredServiceSimpleFormController 
</prop> 

<prop 
key="/services/edit.html"> 
editRegisteredServiceSimpleFormController 
</prop> 

<prop 
key="/services/loggedOut.html"> 
serviceLogoutViewController 
</prop> 

<prop key="/services/viewStatistics.html"> 
viewStatisticsController 
</prop> 

<prop 
key="/services/*"> 
manageRegisteredServicesMultiActionController 
</prop> 
<prop 
key="/openid/*">openIdProviderController</prop> 
<prop 
key="/authorizationFailure.html">passThroughController</prop> 
</props> 
</property> 
<property 
name="alwaysUseFullPath" value="true" /> 
<!-- 
uncomment this to enable sending PageRequest events. 
<property 
name="interceptors"> 
<list> 
<ref bean="pageRequestHandlerInterceptorAdapter" /> 
</list> 
</property> 
--> 
</bean> 

  <bean
    id="handlerMappingC"
    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property
      name="mappings">
      <props>
        <prop
          key="/logout">
          logoutController
        </prop>
        <prop
          key="/serviceValidate">
          serviceValidateController
        </prop>
        <prop
          key="/validate">
          legacyValidateController
        </prop>
        <prop
          key="/proxy">
          proxyController
        </prop>
        <prop
          key="/proxyValidate">
          proxyValidateController
        </prop>
        <prop
          key="/samlValidate">
          samlValidateController
        </prop>
        
        <prop
          key="/services/add.html">
          addRegisteredServiceSimpleFormController
        </prop>
        
        <prop
          key="/services/edit.html">
          editRegisteredServiceSimpleFormController
        </prop>
        
        <prop
          key="/services/loggedOut.html">
          serviceLogoutViewController
        </prop>

<prop key="/services/viewStatistics.html">
viewStatisticsController
</prop>
      
        <prop
          key="/services/*">
          manageRegisteredServicesMultiActionController
        </prop>
        <prop
          key="/openid/*">openIdProviderController</prop>
<prop
key="/authorizationFailure.html">passThroughController</prop>
      </props>
    </property>
    <property
      name="alwaysUseFullPath" value="true" />
    <!--
    uncomment this to enable sending PageRequest events. 
    <property
      name="interceptors">
      <list>
        <ref bean="pageRequestHandlerInterceptorAdapter" />
      </list>
    </property>
     -->
  </bean>

这里有很多映射关系,我们关注/services/viewStatistics.html这个地址 
在浏览器中打开(系统会先提示你登陆,如果你跟我的配置一样,那么admin/admin即可登陆) 


里面红色提示内容大体是说 
因为你没有配置提供的服务,所以CAS处于开放模式,一旦你配置了一个服务,CAS将不再是开放模式,任何应用希望提供CAS,则必须注册。如果你要用这个工具,第一件事是需要将自己加入到这个工具中,默认服务管理工具的URL为"http://cas.boc.com:8080/casServer/services/j_acegi_cas_security_check" 

这里我们先不要去管它,因为要做的工作还有很多,这里只不过是刚刚开始。 

下一步对Client端进行配置 

分享到:
评论

相关推荐

    单点登录sso-shiro-cas-maven

    spring下使用shiro+cas配置单点登录,多个系统之间的访问,每次只需要登录一次 ## 系统模块说明 1. cas: 单点登录模块,这里直接拿的是cas的项目改了点样式而已 2. doc: 文档目录,里面有数据库生成语句,采用的...

    spring boot 实现SSO单点登陆

    spring boot整合spring security 实现SSO单点登陆 完整DEMO. 1、配置本地hosts 127.0.0.1 sso-login 127.0.0.1 sso-resource 127.0.0.1 sso-tmall 127.0.0.1 sso-taobao windows系统的路径在C:\WINDOWS\system...

    CAS单点登录框架整合Spring Security

    CAS 包含两个部分: CAS Server 和 CAS Client ...CAS Client:就是开发过程中的web层, 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。不需要对这个部分进行过多编码,进行简单配置即可。

    spring security 参考手册中文版

    Spring Security 参考 1 第一部分前言 15 1.入门 16 2.介绍 17 2.1什么是Spring Security? 17 2.2历史 19 2.3版本编号 20 2.4获得Spring安全 21 2.4.1使用Maven 21 Maven仓库 21 Spring框架 22 2.4.2 Gradle 23 ...

    cas server 5.3.9 整合数据库验证用户信息,使用security密码验证方式

    提供了cas server 5.3.9 单点登录的资源整合,cas服务器,运行即可用 配置了spring srcurity 密码验证方式

    cas统一身份认证sso

    统一身份认证-CAS配置实现 SSO单点登录Spring-Security+&+CAS+使用手册 统一身份认证-CAS配置实现 CAS登录验证(密码MD5、SHA加密后_再进行Base64加密实现代码)_与Liferay的用户身份验证对应

    sso-poc:使用 Jasig Cas 和 Spring Framework 在概念证明上签名

    单点登录概念证明 原料: 贾西格·卡斯 弹簧框架 PostgreSQL Maven 数据库设置: 使用密码 caliga 创建用户 caliga CREATE USER caliga WITH PASSWORD 'caliga'; 创建数据库 caliga CREATE DATABASE caliga ...

    pro:企业管理应用系统

    6、CAS单点登录-&gt;jasig 耶鲁大学 的cas单点登录服务器,需要单点部署在一台服务器上,sql脚本在pro中的doc的db文件夹中 7、具体效果查看博客: 8、其他相关项目: 一、未集成cas单点的权限管理系统 二、集成cas单点...

    cas:Apereo CAS-适用于所有人及以后的企业单一登录

    CAS是用于Web的企业多语言单点登录解决方案,它试图成为满足身份验证和授权需求的综合平台。 CAS是一个开放且有据可查的身份验证协议。 该协议的主要实现是此处托管的具有相同名称的开源Java服务器组件,并支持大量...

    JAVA上百实例源码以及开源项目源代码

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    JAVA上百实例源码以及开源项目

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

Global site tag (gtag.js) - Google Analytics