用什么网站做一手房最好,网站关键词锚文本指向,室内装修设计学习网,wordpress the_title() 字数oidc“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕#xff1f; 尝试使用Okta API进行托管身份验证#xff0c;授权和多因素身份验证。 Java EE允许您使用JAX-RS和JPA快速轻松地构建Java REST API。 Java EE是保护伞标… oidc “我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕 尝试使用Okta API进行托管身份验证授权和多因素身份验证。 Java EE允许您使用JAX-RS和JPA快速轻松地构建Java REST API。 Java EE是保护伞标准规范它描述了许多Java技术包括EJBJPAJAX-RS和许多其他技术。 它最初旨在允许Java应用程序服务器之间的可移植性并在2000年代初期蓬勃发展。 那时应用程序服务器风行一时并由许多知名公司例如IBMBEA和Sun提供。 JBoss是一家新兴公司它破坏了现状并表明有可能将Java EE应用服务器开发为一个开源项目并免费提供它。 JBoss在2006年被RedHat收购。 在2000年代初期Java开发人员使用servlet和EJB来开发其服务器应用程序。 Hibernate和Spring分别于2002年和2004年问世。 两种技术都对各地的Java开发人员产生了巨大的影响表明他们可以编写没有EJB的分布式健壮的应用程序。 Hibernate的POJO模型最终被用作JPA标准并且对EJB的影响也很大。 快进到2018年Java EE肯定不像以前那样 现在它主要是POJO和注释并且使用起来更简单。 为什么要使用Java EE而不是Spring Boot构建Java REST API Spring Boot是Java生态系统中我最喜欢的技术之一。 它极大地减少了Spring应用程序中必需的配置并使得仅用几行代码即可生成REST API。 但是最近有一些不使用Spring Boot的开发人员提出了许多API安全性问题。 其中一些甚至没有使用Spring 出于这个原因我认为构建一个Java REST API使用Java EE很有趣该API与我过去开发的Spring Boot REST API相同。 即我的Bootiful Angular和Bootiful React帖子中的“啤酒” API。 使用Java EE构建Java REST API 首先我在Twitter上询问了我的网络是否存在诸如start.spring.io之类的Java EE快速入门。 我收到了一些建议并开始进行一些研究。 David Blevins建议我看一下tomee-jaxrs-starter-project 所以我从那里开始。 我还研究了Roberto Cortez推荐的TomEE Maven原型 。 我喜欢jaxrs-starter项目因为它展示了如何使用JAX-RS创建REST API。 TomEE Maven原型也很有用特别是因为它展示了如何使用JPAH2和JSF。 我将两者结合在一起创建了自己的最小启动器可用于在TomEE上实现安全的Java EE API。 您不必在这些示例中使用TomEE但是我尚未在其他实现上对其进行测试。 如果您在其他应用服务器上使用了这些示例请告诉我我将更新此博客文章。 在这些示例中我将使用Java 8和Java EE 7.0以及TomEE 7.1.0。 TomEE 7.x是EE 7兼容版本 有一个TomEE 8.x分支用于EE8兼容性工作但尚无发行版本。 我希望您也安装了Apache Maven 。 首先将我们的Java EE REST API存储库克隆到您的硬盘驱动器然后运行它 git clone https://github.com/oktadeveloper/okta-java-ee-rest-api-example.git javaee-rest-api
cd javaee-rest-api
mvn package tomee:run 导航到http// localhost8080并添加新啤酒。 单击添加 您应该看到成功消息。 单击查看存在的啤酒以查看啤酒的完整列表。 您还可以在http://localhost:8080/good-beers查看系统中的优质啤酒列表。 以下是使用HTTPie时的输出。 $ http :8080/good-beers
HTTP/1.1 200
Content-Type: application/json
Date: Wed, 29 Aug 2018 21:58:23 GMT
Server: Apache TomEE
Transfer-Encoding: chunked[{id: 101,name: Kentucky Brunch Brand Stout},{id: 102,name: Marshmallow Handjee},{id: 103,name: Barrel-Aged Abraxas},{id: 104,name: Heady Topper},{id: 108,name: White Rascal}
]使用Java EE构建REST API 我向您展示了该应用程序可以做什么但是我还没有谈论它是如何构建的。 它有一些XML配置文件但是我将跳过其中的大多数。 目录结构如下所示 $ tree .
.
├── LICENSE
├── README.md
├── pom.xml
└── src├── main│ ├── java│ │ └── com│ │ └── okta│ │ └── developer│ │ ├── Beer.java│ │ ├── BeerBean.java│ │ ├── BeerResource.java│ │ ├── BeerService.java│ │ └── StartupBean.java│ ├── resources│ │ └── META-INF│ │ └── persistence.xml│ └── webapp│ ├── WEB-INF│ │ ├── beans.xml│ │ └── faces-config.xml│ ├── beer.xhtml│ ├── index.jsp│ └── result.xhtml└── test└── resources└── arquillian.xml12 directories, 16 files 最重要的XML文件是pom.xml 它定义了依赖关系并允许您运行TomEE Maven插件。 它很短很甜只有一个依赖项和一个插件。 ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.okta.developer/groupIdartifactIdjava-ee-rest-api/artifactIdversion1.0-SNAPSHOT/versionpackagingwar/packagingnameJava EE Webapp with JAX-RS API/nameurlhttp://developer.okta.com/urlpropertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncodingproject.reporting.outputEncodingUTF-8/project.reporting.outputEncodingmaven.compiler.target1.8/maven.compiler.targetmaven.compiler.source1.8/maven.compiler.sourcefailOnMissingWebXmlfalse/failOnMissingWebXmljavaee-api.version7.0/javaee-api.versiontomee.version7.1.0/tomee.version/propertiesdependenciesdependencygroupIdjavax/groupIdartifactIdjavaee-api/artifactIdversion${javaee-api.version}/versionscopeprovided/scope/dependency/dependenciesbuildpluginsplugingroupIdorg.apache.tomee.maven/groupIdartifactIdtomee-maven-plugin/artifactIdversion${tomee.version}/versionconfigurationcontextROOT/context/configuration/plugin/plugins/build
/project 主要实体是Beer.java 。 package com.okta.developer;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;Entity
public class Beer {IdGeneratedValue(strategy GenerationType.AUTO)private int id;private String name;public Beer() {}public Beer(String name) {this.name name;}public int getId() {return id;}public void setId(int id) {this.id id;}public String getName() {return name;}public void setName(String beerName) {this.name beerName;}Overridepublic String toString() {return Beer{ id id , name name \ };}
} 数据库又名数据源在src/main/resources/META-INF/persistence.xml 。 ?xml version1.0 encodingUTF-8?
persistence version2.0 xmlnshttp://java.sun.com/xml/ns/persistencexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsdpersistence-unit namebeer-pu transaction-typeJTAjta-data-sourcebeerDatabase/jta-data-sourceclasscom.okta.developer.Beer/classpropertiesproperty nameopenjpa.jdbc.SynchronizeMappings valuebuildSchema(ForeignKeystrue)//properties/persistence-unit
/persistence BeerService.java类使用JPA的EntityManager处理该实体的读取并将其保存到数据库。 package com.okta.developer;import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;
import java.util.List;Stateless
public class BeerService {PersistenceContext(unitName beer-pu)private EntityManager entityManager;public void addBeer(Beer beer) {entityManager.persist(beer);}public ListBeer getAllBeers() {CriteriaQueryBeer cq entityManager.getCriteriaBuilder().createQuery(Beer.class);cq.select(cq.from(Beer.class));return entityManager.createQuery(cq).getResultList();}public void clear() {Query removeAll entityManager.createQuery(delete from Beer);removeAll.executeUpdate();}
} 有一个StartupBean.java 用于在启动时填充数据库并在关闭时清除数据库。 package com.okta.developer;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;
import java.util.stream.Stream;Singleton
Startup
public class StartupBean {private final BeerService beerService;Injectpublic StartupBean(BeerService beerService) {this.beerService beerService;}PostConstructprivate void startup() {// Top beers from https://www.beeradvocate.com/lists/top/Stream.of(Kentucky Brunch Brand Stout, Marshmallow Handjee, Barrel-Aged Abraxas, Heady Topper,Budweiser, Coors Light, PBR).forEach(name -beerService.addBeer(new Beer(name)));beerService.getAllBeers().forEach(System.out::println);}PreDestroyprivate void shutdown() {beerService.clear();}
} 这三个类构成了应用程序的基础还有一个BeerResource.java类它使用JAX-RS公开/good-beers端点。 package com.okta.developer;import javax.ejb.Lock;
import javax.ejb.Singleton;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.List;
import java.util.stream.Collectors;import static javax.ejb.LockType.READ;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;Lock(READ)
Singleton
Path(/good-beers)
public class BeerResource {private final BeerService beerService;Injectpublic BeerResource(BeerService beerService) {this.beerService beerService;}GETProduces({APPLICATION_JSON})public ListBeer getGoodBeers() {return beerService.getAllBeers().stream().filter(this::isGreat).collect(Collectors.toList());}private boolean isGreat(Beer beer) {return !beer.getName().equals(Budweiser) !beer.getName().equals(Coors Light) !beer.getName().equals(PBR);}
} 最后有一个BeerBean.java类用作JSF的托管bean。 package com.okta.developer;import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.List;Named
RequestScoped
public class BeerBean {Injectprivate BeerService beerService;private ListBeer beersAvailable;private String name;public String getName() {return name;}public void setName(String name) {this.name name;}public ListBeer getBeersAvailable() {return beersAvailable;}public void setBeersAvailable(ListBeer beersAvailable) {this.beersAvailable beersAvailable;}public String fetchBeers() {beersAvailable beerService.getAllBeers();return success;}public String add() {Beer beer new Beer();beer.setName(name);beerService.addBeer(beer);return success;}
} 您现在拥有了使用Java EE构建的REST API 但是这并不安全。 在以下各节中我将向您展示如何使用Okta的Java JWT验证程序Spring Security和Pac4j保护它。 使用Okta将OIDC安全性添加到Java REST API 您将需要在Okta中创建OIDC应用程序以验证将要实施的安全配置。 要使此操作毫不费力可以使用Okta的OIDC API。 在Okta我们的目标是使身份管理比您以往更加轻松安全和可扩展。 Okta是一项云服务允许开发人员创建编辑和安全地存储用户帐户和用户帐户数据并将它们与一个或多个应用程序连接。 我们的API使您能够 验证和授权您的用户 存储有关您的用户的数据 执行基于密码的社交登录 通过多因素身份验证保护您的应用程序 以及更多 查看我们的产品文档 你卖了吗 立即注册一个永久免费的开发者帐户 完成后请完成以下步骤以创建OIDC应用程序。 登录到您在developer.okta.com上的开发者帐户。 导航至应用程序 然后单击添加应用程序 。 选择Web 然后单击Next 。 为应用程序命名例如Java EE Secure API 然后添加以下内容作为登录重定向URI http://localhost:3000/implicit/callback http://localhost:8080/login/oauth2/code/okta http://localhost:8080/callback?client_nameOidcClient 单击完成 然后编辑项目并启用“隐式混合”作为授予类型允许ID和访问令牌然后单击保存 。 使用JWT Verifier保护Java REST API 要从Okta验证JWT您需要将Okta Java JWT Verifier添加到pom.xml 。 properties...okta-jwt.version0.3.0/okta-jwt.version
/propertiesdependencies...dependencygroupIdcom.okta.jwt/groupIdartifactIdokta-jwt-verifier/artifactIdversion${okta-jwt.version}/version/dependency
/dependencies 然后创建一个JwtFilter.java 在src/main/java/com/okta/developer目录中。 此过滤器查找其中包含访问令牌的authorization标头。 如果存在它将对其进行验证并打印出用户的sub 也就是他们的电子邮件地址。 如果不存在或无效则返回拒绝访问状态。 确保使用您创建的应用中的设置替换{yourOktaDomain}和{clientId} 。 package com.okta.developer;import com.nimbusds.oauth2.sdk.ParseException;
import com.okta.jwt.JoseException;
import com.okta.jwt.Jwt;
import com.okta.jwt.JwtHelper;
import com.okta.jwt.JwtVerifier;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;WebFilter(filterName jwtFilter, urlPatterns /*)
public class JwtFilter implements Filter {private JwtVerifier jwtVerifier;Overridepublic void init(FilterConfig filterConfig) {try {jwtVerifier new JwtHelper().setIssuerUrl(https://{yourOktaDomain}/oauth2/default).setClientId({yourClientId}).build();} catch (IOException | ParseException e) {System.err.print(Configuring JWT Verifier failed!);e.printStackTrace();}}Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain chain) throws IOException, ServletException {HttpServletRequest request (HttpServletRequest) servletRequest;HttpServletResponse response (HttpServletResponse) servletResponse;System.out.println(In JwtFilter, path: request.getRequestURI());// Get access token from authorization headerString authHeader request.getHeader(authorization);if (authHeader null) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, Access denied.);return;} else {String accessToken authHeader.substring(authHeader.indexOf(Bearer ) 7);try {Jwt jwt jwtVerifier.decodeAccessToken(accessToken);System.out.println(Hello, jwt.getClaims().get(sub));} catch (JoseException e) {e.printStackTrace();response.sendError(HttpServletResponse.SC_UNAUTHORIZED, Access denied.);return;}}chain.doFilter(request, response);}Overridepublic void destroy() {}
} 为确保此过滤器正常工作请重新启动您的应用并运行 mvn package tomee:run 如果在浏览器中导航到http://localhost:8080/good-beers 则会看到拒绝访问错误。 为了证明它可以与有效的JWT一起使用您可以克隆我的Bootiful React项目并运行其UI git clone -b okta https://github.com/oktadeveloper/spring-boot-react-example.git bootiful-react
cd bootiful-react/client
npm install 编辑此项目的client/src/App.tsx文件并更改issuer和clientId以匹配您的应用程序。 const config {issuer: https://{yourOktaDomain}/oauth2/default,redirectUri: window.location.origin /implicit/callback,clientId: {yourClientId}
}; 然后启动它 npm start 然后您应该能够使用创建帐户所用的凭据登录http://localhost:3000 。 但是由于CORS错误在浏览器的开发人员控制台中您将无法从API加载任何啤酒。 Failed to load http://localhost:8080/good-beers: Response to preflight request doesnt pass access control check: No Access-Control-Allow-Origin header is present on the requested resource. Origin http://localhost:3000 is therefore not allowed access. 提示如果看到401并且没有CORS错误则可能意味着您的客户ID不匹配。 要解决此CORS错误请在JwtFilter.java类旁边添加一个CorsFilter.java 。 下面的过滤器将允许OPTIONS请求并向后发送访问控制标头该标头允许任何原始GET方法和任何标头。 我建议您在生产中使这些设置更加具体。 package com.okta.developer;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;WebFilter(filterName corsFilter)
public class CorsFilter implements Filter {Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)throws IOException, ServletException {HttpServletRequest request (HttpServletRequest) servletRequest;HttpServletResponse response (HttpServletResponse) servletResponse;System.out.println(In CorsFilter, method: request.getMethod());// Authorize (allow) all domains to consume the contentresponse.addHeader(Access-Control-Allow-Origin, http://localhost:3000);response.addHeader(Access-Control-Allow-Methods, GET);response.addHeader(Access-Control-Allow-Headers, *);// For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshakeif (request.getMethod().equals(OPTIONS)) {response.setStatus(HttpServletResponse.SC_ACCEPTED);return;}// pass the request along the filter chainchain.doFilter(request, response);}Overridepublic void init(FilterConfig config) {}Overridepublic void destroy() {}
} 您添加的两个过滤器都使用WebFilter进行注册。 这是一个方便的注释但不提供任何过滤器排序功能。 要解决此缺失的功能请修改JwtFilter 使其在WebFilter中没有urlPattern 。 WebFilter(filterName jwtFilter) 然后创建一个src/main/webapp/WEB-INF/web.xml文件并使用以下XML进行填充。 这些过滤器映射可确保CorsFilter处理CorsFilter 。 ?xml version1.0 encodingUTF-8?
web-app version3.1xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsdfilter-mappingfilter-namecorsFilter/filter-nameurl-pattern/*/url-pattern/filter-mappingfilter-mappingfilter-namejwtFilter/filter-nameurl-pattern/*/url-pattern/filter-mapping
/web-app 重新启动Java API现在一切正常 在控制台中您应该看到类似于我的消息 In CorsFilter, method: OPTIONS
In CorsFilter, method: GET
In JwtFilter, path: /good-beers
Hello, demookta.com 使用Okta的JWT验证程序过滤器是实现资源服务器的一种简单方法采用OAuth 2.0命名法。 但是它不向您提供有关该用户的任何信息。 JwtVerifier接口的确有一个decodeIdToken(String idToken, String nonce)方法但是您必须从客户端传递ID令牌才能使用它。 在接下来的两节中我将向您展示如何使用Spring Security和Pac4j来实现类似的安全性。 另外我将向您展示如何提示用户登录当他们尝试直接访问API时并获取用户的信息。 通过Spring Security保护Java REST API Spring Security是我在Javaland中最喜欢的框架之一。 在显示如何使用Spring Security时此博客上的大多数示例都使用Spring Boot。 我将使用最新版本– 5.1.0.RC2 –因此本教程将保持最新状态。 还原更改以添加JWT Verifier或直接删除web.xml继续。 修改您的pom.xml使其具有Spring Security所需的依赖关系。 您还需要添加Spring的快照存储库以获得候选版本。 properties...spring-security.version5.1.0.RC2/spring-security.versionspring.version5.1.0.RC3/spring.versionjackson.version2.9.6/jackson.version
/propertiesdependencyManagementdependenciesdependencygroupIdorg.springframework/groupIdartifactIdspring-framework-bom/artifactIdversion${spring.version}/versionscopeimport/scopetypepom/type/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-bom/artifactIdversion${spring-security.version}/versionscopeimport/scopetypepom/type/dependency/dependencies
/dependencyManagementdependencies...dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactId/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-web/artifactId/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-config/artifactId/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-oauth2-client/artifactId/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-oauth2-resource-server/artifactId/dependencydependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-oauth2-jose/artifactId/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-core/artifactIdversion${jackson.version}/version/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion${jackson.version}/version/dependency
/dependenciespluginRepositoriespluginRepositoryidspring-snapshots/idnameSpring Snapshots/nameurlhttps://repo.spring.io/libs-snapshot/urlsnapshotsenabledtrue/enabled/snapshots/pluginRepository
/pluginRepositories
repositoriesrepositoryidspring-snapshots/idnameSpring Snapshot/nameurlhttps://repo.spring.io/libs-snapshot/url/repository
/repositories 在src/main/java/com/okta/developer创建一个SecurityWebApplicationInitializer.java类 package com.okta.developer;import org.springframework.security.web.context.*;public class SecurityWebApplicationInitializerextends AbstractSecurityWebApplicationInitializer {public SecurityWebApplicationInitializer() {super(SecurityConfiguration.class);}
} 在同一目录中创建一个SecurityConfiguration.java类。 此类使用Spring Security 5的oauth2Login()并向Spring Security注册您的Okta应用程序。 package com.okta.developer;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.context.annotation.PropertySource;
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.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;Configuration
EnableWebSecurity
PropertySource(classpath:application.properties)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {private final String clientSecret;private final String clientId;private final String issuerUri;Autowiredpublic SecurityConfiguration(Value(${okta.issuer-uri}) String issuerUri,Value(${okta.client-id}) String clientId,Value(${okta.client-secret}) String clientSecret) {this.issuerUri issuerUri;this.clientId clientId;this.clientSecret clientSecret;}Overrideprotected void configure(HttpSecurity http) throws Exception {http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and().authorizeRequests().anyRequest().authenticated().and().oauth2Login();}Beanpublic OAuth2AuthorizedClientService authorizedClientService() {return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository());}Beanpublic ClientRegistrationRepository clientRegistrationRepository() {ListClientRegistration registrations clients.stream().map(this::getRegistration).filter(Objects::nonNull).collect(Collectors.toList());return new InMemoryClientRegistrationRepository(registrations);}Beanpublic ClientRegistrationRepository clientRegistrationRepository() {ClientRegistration okta getRegistration();return new InMemoryClientRegistrationRepository(okta);}ClientRegistrations.fromOidcIssuerLocation(Objects.requireNonNull(issuerUri)).registrationId(okta).clientId(clientId).clientSecret(clientSecret).build();
} 创建src/main/resources/application.properties并用Okta OIDC应用设置进行填充。 okta.client-id{clientId}
okta.client-secret{clientSecret}
okta.issuer-urihttps://{yourOktaDomain}/oauth2/default 感谢Baeldung提供有关Spring Security 5 OAuth的出色文档 。 因为启用了CSRF所以必须在任何h:form标记内添加以下隐藏字段以保护CSRF。 我将以下内容添加到src/main/webapp/beer.xhtml和result.xhtml 。 input typehidden value${_csrf.token} name${_csrf.parameterName}/ 重新启动您的API mvn clean package tomee:run 并导航到http://localhost:8080/good-beers 。 您应该重定向到Okta进行登录。 输入有效的凭证您应该在浏览器中看到JSON。 JSON Viewer Chrome插件提供了美观的JSON。 要求用户登录以查看您的API数据很方便但是最好将其作为React UI示例的资源服务器。 OAuth 2.0资源服务器支持是Spring Security 5.1.0 RC1中的新增功能因此我将向您展示如何使用它。 用以下代码替换SecurityConfiguration.java的configure()方法该代码启用CORS并设置资源服务器。 Override
protected void configure(HttpSecurity http) throws Exception {http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and().cors().and().authorizeRequests().anyRequest().authenticated().and().oauth2Login().and().oauth2ResourceServer().jwt();
}Bean
JwtDecoder jwtDecoder() {return JwtDecoders.fromOidcIssuerLocation(this.issuerUri);
}Bean
CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration new CorsConfiguration();configuration.setAllowCredentials(true);configuration.setAllowedOrigins(Collections.singletonList(http://localhost:3000));configuration.setAllowedMethods(Collections.singletonList(GET));configuration.setAllowedHeaders(Collections.singletonList(*));UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration(/**, configuration);return source;
} 进行这些更改后重新启动您的API并确认您的React UI可以与之对话。 很漂亮吧 Spring Security的用户信息 Spring Security与Servlet API集成在一起因此您可以使用以下方法来获取当前用户的信息。 HttpServletRequest.getRemoteUser() HttpServletRequest.getUserPrincipal() 拥有Principal 您可以获取有关用户的详细信息包括其角色又名权限。 OAuth2Authentication authentication (OAuth2Authentication) principal;
MapString, Object user (MapString, Object) authentication.getUserAuthentication().getDetails(); 请参阅Spring Security的Servlet API集成文档以获取更多信息。 使用Pac4j锁定Java REST API 我想向您展示的确保Java REST API安全的最后一种技术是使用Pac4j特别是j2e-pac4j 。 恢复您的更改以添加Spring Security。 git reset --hard HEAD 编辑pom.xml以添加完成本节所需的Pac4j库。 properties...pac4j-j2e.version4.0.0/pac4j-j2e.versionpac4j.version3.0.0/pac4j.version
/propertiesdependencies...dependencygroupIdorg.pac4j/groupIdartifactIdj2e-pac4j/artifactIdversion${pac4j-j2e.version}/version/dependencydependencygroupIdorg.pac4j/groupIdartifactIdpac4j-oidc/artifactIdversion${pac4j.version}/version/dependencydependencygroupIdorg.pac4j/groupIdartifactIdpac4j-http/artifactIdversion${pac4j.version}/version/dependencydependencygroupIdorg.pac4j/groupIdartifactIdpac4j-jwt/artifactIdversion${pac4j.version}/version/dependency
/dependencies 就像创建JWT Verifier一样创建一个src/main/java/com/okta/developer/CorsFilter.java 。 package com.okta.developer;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;WebFilter(filterName corsFilter)
public class CorsFilter implements Filter {Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)throws IOException, ServletException {HttpServletRequest request (HttpServletRequest) servletRequest;HttpServletResponse response (HttpServletResponse) servletResponse;System.out.println(In CorsFilter, method: request.getMethod());// Authorize (allow) all domains to consume the contentresponse.addHeader(Access-Control-Allow-Origin, http://localhost:3000);response.addHeader(Access-Control-Allow-Methods, GET);response.addHeader(Access-Control-Allow-Headers, *);// For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshakeif (request.getMethod().equals(OPTIONS)) {response.setStatus(HttpServletResponse.SC_ACCEPTED);return;}// pass the request along the filter chainchain.doFilter(request, response);}Overridepublic void init(FilterConfig config) {}Overridepublic void destroy() {}
} 在同一程序包中创建一个SecurityConfigFactory.java 。 将客户端ID密钥和域占位符替换为与OIDC应用程序匹配的占位符。 package com.okta.developer;import com.fasterxml.jackson.databind.ObjectMapper;
import org.pac4j.core.client.Clients;
import org.pac4j.core.client.direct.AnonymousClient;
import org.pac4j.core.config.Config;
import org.pac4j.core.config.ConfigFactory;
import org.pac4j.core.credentials.TokenCredentials;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.http.client.direct.HeaderClient;
import org.pac4j.jwt.config.signature.RSASignatureConfiguration;
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator;
import org.pac4j.jwt.util.JWKHelper;
import org.pac4j.oidc.client.OidcClient;
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.oidc.profile.OidcProfile;import java.io.IOException;
import java.net.URL;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;public class SecurityConfigFactory implements ConfigFactory {private final JwtAuthenticator jwtAuthenticator new JwtAuthenticator();private final ObjectMapper mapper new ObjectMapper();Overridepublic Config build(final Object... parameters) {System.out.print(Building Security configuration...\n);final OidcConfiguration oidcConfiguration new OidcConfiguration();oidcConfiguration.setClientId({yourClientId});oidcConfiguration.setSecret({yourClientSecret});oidcConfiguration.setDiscoveryURI(https://{yourOktaDomain}/oauth2/default/.well-known/openid-configuration);oidcConfiguration.setUseNonce(true);final OidcClientOidcProfile, OidcConfiguration oidcClient new OidcClient(oidcConfiguration);oidcClient.setAuthorizationGenerator((ctx, profile) - {profile.addRole(ROLE_USER);return profile;});HeaderClient headerClient new HeaderClient(Authorization, Bearer , (credentials, ctx) - {String token ((TokenCredentials) credentials).getToken();if (token ! null) {try {// Get JWKURL keysUrl new URL(https://{yourOktaDomain}/oauth2/default/v1/keys);Map map mapper.readValue(keysUrl, Map.class);List keys (ArrayList) map.get(keys);String json mapper.writeValueAsString(keys.get(0));// Build key pair and validate tokenKeyPair rsaKeyPair JWKHelper.buildRSAKeyPairFromJwk(json);jwtAuthenticator.addSignatureConfiguration(new RSASignatureConfiguration(rsaKeyPair));CommonProfile profile jwtAuthenticator.validateToken(token);credentials.setUserProfile(profile);System.out.println(Hello, profile.getId());} catch (IOException e) {System.err.println(Failed to validate Bearer token: e.getMessage());e.printStackTrace();}}});final Clients clients new Clients(http://localhost:8080/callback,oidcClient, headerClient, new AnonymousClient());return new Config(clients);}
} 如果oidcClient的代码中的oidcClient尝试直接访问您的API则将使用户登录Okta。 headerClient设置了一个资源服务器该资源服务器根据用户的访问令牌对用户进行授权。 创建src/main/webapp/WEB-INF/web.xml来映射CorsFilter以及Pac4j的CallbackFilter和SecurityFilter 。 您可以看到SecurityFilter通过其configFactory init-param链接到SecurityConfigFactory类。 ?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsdversion3.1display-namejavaee-pac4j-demo/display-nameabsolute-ordering/filter-mappingfilter-namecorsFilter/filter-nameurl-pattern/*/url-pattern/filter-mappingfilterfilter-namecallbackFilter/filter-namefilter-classorg.pac4j.j2e.filter.CallbackFilter/filter-classinit-paramparam-namedefaultUrl/param-nameparam-value//param-value/init-paraminit-paramparam-namerenewSession/param-nameparam-valuetrue/param-value/init-paraminit-paramparam-namemultiProfile/param-nameparam-valuetrue/param-value/init-param/filterfilter-mappingfilter-namecallbackFilter/filter-nameurl-pattern/callback/url-patterndispatcherREQUEST/dispatcher/filter-mappingfilterfilter-nameOidcFilter/filter-namefilter-classorg.pac4j.j2e.filter.SecurityFilter/filter-classinit-paramparam-nameconfigFactory/param-nameparam-valuecom.okta.developer.SecurityConfigFactory/param-value/init-paraminit-paramparam-nameclients/param-nameparam-valueoidcClient,headerClient/param-value/init-paraminit-paramparam-nameauthorizers/param-nameparam-valuesecurityHeaders/param-value/init-param/filterfilter-mappingfilter-nameOidcFilter/filter-nameurl-pattern/*/url-patterndispatcherREQUEST/dispatcherdispatcherFORWARD/dispatcher/filter-mapping
/web-app 为了更好地可视化用户信息您需要再创建一些文件。 这些与JSF相关的文件是从j2e-pac4j-cdi-demo复制的。 注意我试图在TomEE上运行j2e-pac4j-cdi-demo 没有web.xml 但是它失败并出现错误 Filters cannot be added to context [] as the context has been initialised 。 当使用Payara Maven插件时它确实起作用。 创建src/main/java/com/okta/developer/ProfileView.java 这是一个JSF托管的bean用于收集用户的信息。 package com.okta.developer;import org.pac4j.core.context.WebContext;
import org.pac4j.core.profile.ProfileManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.List;/*** Managed bean which exposes the pac4j profile manager.** JSF views such as facelets can reference this to view the contents of profiles.** author Phillip Ross*/
Named
RequestScoped
public class ProfileView {/** The static logger instance. */private static final Logger logger LoggerFactory.getLogger(ProfileView.class);/** The pac4j web context. */Injectprivate WebContext webContext;/** The pac4j profile manager. */Injectprivate ProfileManager profileManager;/** Simple no-args constructor. */public ProfileView() {}/*** Gets the first profile (if it exists) contained in the profile manager.** return a list of pac4j profiles*/public Object getProfile() {return profileManager.get(true).orElse(null); // Its fine to return a null reference if there is no value present.}/*** Gets the profiles contained in the profile manager.** return a list of pac4j profiles*/public List getProfiles() {return profileManager.getAll(true);}/** Simply prints some debugging information post-construction. */PostConstructpublic void init() {logger.debug(webContext is null? {}, (webContext null));logger.debug(profileManager is null? {}, (profileManager null));}
} 将src/main/webapp/oidc/index.xhtml为JSF模板。 ui:composition xmlnshttp://www.w3.org/1999/xhtmlxmlns:hhttp://java.sun.com/jsf/htmlxmlns:fhttp://java.sun.com/jsf/corexmlns:uihttp://java.sun.com/jsf/faceletstemplate/WEB-INF/template.xhtmlui:define nametitlePac4J Java EE Demo - Protected Area/ui:defineui:define namecontentdiv classui-gdiv classui-g-12div classui-containerh1Protected Area/h1ph:link valueBack outcome/index//p/divui:include src/WEB-INF/facelets/includes/pac4j-profiles-list.xhtml//div/div/ui:define
/ui:composition 创建pac4j-profiles-list.xhtml文件该文件包含在WEB-INF/facelets/includes 。 ui:composition xmlnshttp://www.w3.org/1999/xhtmlxmlns:hhttp://java.sun.com/jsf/htmlxmlns:fhttp://java.sun.com/jsf/corexmlns:uihttp://java.sun.com/jsf/faceletsdiv classui-containerpFound h:outputText value#{profileView.profiles.size()}/ profiles./ph:panelGroup layoutblock rendered#{profileView.profiles.size() 0}pFirst profile: h:outputText value#{profileView.profile}//p/h:panelGroup/divh:panelGroup layoutblock rendered#{not empty profileView.profile}h2Profile Details/h2ph:outputText valueId: #{profileView.profile.id}//pph:outputText valueType Id: #{profileView.profile.typedId}//pph:outputText valueRemembered: #{profileView.profile.remembered}//ph3Attributes (h:outputText value#{profileView.profile.attributes.size()}/)/h3h:panelGroup layoutblock rendered#{profileView.profile.attributes.size() 0}ului:repeat value#{profileView.profile.attributes.keySet().toArray()} varattributeNamelih:outputText value#{attributeName}/: h:outputText value#{profileView.profile.attributes.get(attributeName)}/ /li/ui:repeat/ul/h:panelGrouph3Roles (h:outputText value#{profileView.profile.roles.size()}/)/h3h:panelGroup layoutblock rendered#{profileView.profile.roles.size() 0}ului:repeat value#{profileView.profile.roles.toArray()} varrolelih:outputText value#{role}//li/ui:repeat/ul/h:panelGrouph3Permissions (h:outputText value#{profileView.profile.permissions.size()}/)/h3h:panelGroup layoutblock rendered#{profileView.profile.permissions.size() 0}ului:repeat value#{profileView.profile.permissions.toArray()} varpermissionlih:outputText value#{permission}//li/ui:repeat/ul/h:panelGroup/h:panelGroup
/ui:composition oidc/index.xhtml模板使用WEB-INF/template.xhtml 因此您也需要创建它。 !DOCTYPE html
html xmlnshttp://www.w3.org/1999/xhtmlxmlns:hhttp://java.sun.com/jsf/htmlxmlns:fhttp://java.sun.com/jsf/corexmlns:uihttp://java.sun.com/jsf/faceletsh:headf:facet namefirstmeta http-equivX-UA-Compatible contentIEedge /meta http-equivContent-Type contenttext/html; charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalable0/meta nameapple-mobile-web-app-capable contentyes //f:facettitleui:insert nametitlePac4J Java EE Demo/ui:insert/titleui:insert namehead//h:headh:body styleClassmain-bodydiv classlayout-wrapperdiv classlayout-mainui:insert namecontent//div/div/h:body
/html 添加这些文件后重建项目并重新启动TomEE。 mvn clean package tomee:run 导航到http://localhost:8080/oidc/index.jsf 您将被重定向到Okta进行登录。 如果您初次尝试无法解决问题请重新启动浏览器并使用隐身窗口。 您应该看到用户的个人资料信息。 在http://localhost:3000尝试您的React客户端; 它也应该工作 如果您想知道为什么不堆叠图像那是因为我将React应用程序的BeerList.tsx的啤酒清单的JSX更改为内联。 h2Beer List/h2
{beers.map((beer: Beer) span key{beer.id} style{{float: left, marginRight: 10px, marginLeft: 10px}}{beer.name}br/GiphyImage name{beer.name}//span
)}雅加达EE呢 您可能已经听说Java EE已经成为开源的类似于Java SE的OpenJDK 它的新名称是Jakarta EE 。 David Blevins是一个很好的朋友并且积极参与Java EE / Jakarta EE。 有关证明请参阅他的Twitter传记Apache TomEEOpenEJB和Geronimo项目的创始人。 ApacheJCP ECEE4J PMCJakarta EE WGMicroProfile和Eclipse Board的成员。 首席执行官Tomitribe 。 我问戴维何时会发布可用的Jakarta EE。 David目前的主要重点是创建与Java EE 8兼容的Jakarta EE版本。我们希望在今年年底之前将其发布。 发布之后我们将开始开发Jakarta EE 9并根据需要进行迭代。 Jakarta EE有一个工作组来决定平台的方向。 了解有关安全REST APIJava EEJakarta EE和OIDC的更多信息 我希望您喜欢这个游览向您展示了如何使用JWT和OIDC构建和保护Java EE REST API。 如果您想查看每个完成部分的源代码我将它们放在GitHub repo的分支中。 您可以使用以下命令克隆不同的实现 git clone -b jwt-verifier https://github.com/oktadeveloper/okta-java-ee-rest-api-example.git
git clone -b spring-security https://github.com/oktadeveloper/okta-java-ee-rest-api-example.git
git clone -b pac4j https://github.com/oktadeveloper/okta-java-ee-rest-api-example.git 如前所述我们在此博客上获得的大多数Java教程都展示了如何使用Spring Boot。 如果您对学习Spring Boot感兴趣这里有一些我写的教程将向您展示要点。 Spring BootOAuth 2.0和Okta入门 使用React和Spring Boot构建一个简单的CRUD应用 使用Angular 7.0和Spring Boot 2.1构建基本的CRUD应用 如果您是OIDC的新手建议您查看以下文章 Spring Security 5.0和OIDC入门 身份声明和令牌– OpenID Connect入门第1部分共3部分 行动中的OIDC – OpenID Connect入门第2部分共3部分 令牌中有什么 – OpenID Connect入门第3部分共3部分 有关Java REST API和TomEE的更多信息我建议以下来源 David Blevins –解构REST安全迭代2018 Antonio Goncalves –使用JWT保护JAX-RS端点 TomEE使用Systemd运行 如果您已经做到了这一点我怀疑您可能会对将来的博客文章感兴趣。 在Twitter上关注我和我的整个团队 在Facebook上关注我们或者查看我们的YouTube频道 。 如有疑问请在下面发表评论或将其发布到我们的开发者论坛 。 “我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕 尝试使用Okta API进行托管身份验证授权和多因素身份验证。 使用Java EE和OIDC构建Java REST API最初于2018年9月12日发布在Okta开发人员博客上。 翻译自: https://www.javacodegeeks.com/2018/10/build-java-rest-api-java-ee-oidc.htmloidc