oidc_使用Java EE和OIDC构建Java REST API

oidc

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。

Java EE允许您使用JAX-RS和JPA快速轻松地构建Java REST API。 Java EE是保护伞标准规范,它描述了许多Java技术,包括EJB,JPA,JAX-RS和许多其他技术。 它最初旨在允许Java应用程序服务器之间的可移植性,并在2000年代初期蓬勃发展。 那时,应用程序服务器风行一时,并由许多知名公司(例如IBM,BEA和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原型也很有用,特别是因为它展示了如何使用JPA,H2和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:// localhost:8080并添加新啤酒。

Java REST API

单击添加 ,您应该看到成功消息。

Java REST API

单击查看存在的啤酒查看啤酒的完整列表。

Java REST API
您还可以在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 version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.okta.developer</groupId><artifactId>java-ee-rest-api</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>Java EE Webapp with JAX-RS API</name><url>http://developer.okta.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.source>1.8</maven.compiler.source><failOnMissingWebXml>false</failOnMissingWebXml><javaee-api.version>7.0</javaee-api.version><tomee.version>7.1.0</tomee.version></properties><dependencies><dependency><groupId>javax</groupId><artifactId>javaee-api</artifactId><version>${javaee-api.version}</version><scope>provided</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.tomee.maven</groupId><artifactId>tomee-maven-plugin</artifactId><version>${tomee.version}</version><configuration><context>ROOT</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 {@Id@GeneratedValue(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 version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"><persistence-unit name="beer-pu" transaction-type="JTA"><jta-data-source>beerDatabase</jta-data-source><class>com.okta.developer.Beer</class><properties><property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/></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 List<Beer> getAllBeers() {CriteriaQuery<Beer> 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;}@GET@Produces({APPLICATION_JSON})public List<Beer> 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 List<Beer> beersAvailable;private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public List<Beer> getBeersAvailable() {return beersAvailable;}public void setBeersAvailable(List<Beer> 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应用程序。

  1. 登录到您在developer.okta.com上的开发者帐户。
  2. 导航至应用程序 ,然后单击添加应用程序
  3. 选择Web ,然后单击Next
  4. 为应用程序命名(例如Java EE Secure API ),然后添加以下内容作为登录重定向URI:
    • http://localhost:3000/implicit/callback
    • http://localhost:8080/login/oauth2/code/okta
    • http://localhost:8080/callback?client_name=OidcClient
  5. 单击完成 ,然后编辑项目并启用“隐式(混合)”作为授予类型(允许ID和访问令牌),然后单击保存

使用JWT Verifier保护Java REST API

要从Okta验证JWT,您需要将Okta Java JWT Verifier添加到pom.xml

<properties>...<okta-jwt.version>0.3.0</okta-jwt.version>
</properties><dependencies>...<dependency><groupId>com.okta.jwt</groupId><artifactId>okta-jwt-verifier</artifactId><version>${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 ,则会看到拒绝访问错误。

Java REST API

为了证明它可以与有效的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文件,并更改issuerclientId以匹配您的应用程序。

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 doesn't 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 version="1.0" encoding="UTF-8"?>
<web-app version="3.1"xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"><filter-mapping><filter-name>corsFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><filter-mapping><filter-name>jwtFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
</web-app>

重新启动Java API,现在一切正常!

Java REST API

在控制台中,您应该看到类似于我的消息:

In CorsFilter, method: OPTIONS
In CorsFilter, method: GET
In JwtFilter, path: /good-beers
Hello, demo@okta.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.version>5.1.0.RC2</spring-security.version><spring.version>5.1.0.RC3</spring.version><jackson.version>2.9.6</jackson.version>
</properties><dependencyManagement><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-framework-bom</artifactId><version>${spring.version}</version><scope>import</scope><type>pom</type></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-bom</artifactId><version>${spring-security.version}</version><scope>import</scope><type>pom</type></dependency></dependencies>
</dependencyManagement><dependencies>...<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-client</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-resource-server</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency>
</dependencies><pluginRepositories><pluginRepository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/libs-snapshot</url><snapshots><enabled>true</enabled></snapshots></pluginRepository>
</pluginRepositories>
<repositories><repository><id>spring-snapshots</id><name>Spring Snapshot</name><url>https://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() {List<ClientRegistration> 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-uri=https://{yourOktaDomain}/oauth2/default

感谢Baeldung提供有关Spring Security 5 OAuth的出色文档 。

因为启用了CSRF,所以必须在任何<h:form>标记内添加以下隐藏字段以保护CSRF。 我将以下内容添加到src/main/webapp/beer.xhtmlresult.xhtml

<input type="hidden" value="${_csrf.token}" name="${_csrf.parameterName}"/>

重新启动您的API( mvn clean package tomee:run )并导航到http://localhost:8080/good-beers 。 您应该重定向到Okta进行登录。

Java REST API

输入有效的凭证,您应该在浏览器中看到JSON。 JSON Viewer Chrome插件提供了美观的JSON。

Java REST API

要求用户登录以查看您的API数据很方便,但是最好将其作为React UI示例的资源服务器。 OAuth 2.0资源服务器支持是Spring Security 5.1.0 RC1中的新增功能,因此我将向您展示如何使用它。

用以下代码替换SecurityConfiguration.javaconfigure()方法,该代码启用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;
Map<String, Object> user = (Map<String, 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.version>4.0.0</pac4j-j2e.version><pac4j.version>3.0.0</pac4j.version>
</properties><dependencies>...<dependency><groupId>org.pac4j</groupId><artifactId>j2e-pac4j</artifactId><version>${pac4j-j2e.version}</version></dependency><dependency><groupId>org.pac4j</groupId><artifactId>pac4j-oidc</artifactId><version>${pac4j.version}</version></dependency><dependency><groupId>org.pac4j</groupId><artifactId>pac4j-http</artifactId><version>${pac4j.version}</version></dependency><dependency><groupId>org.pac4j</groupId><artifactId>pac4j-jwt</artifactId><version>${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 OidcClient<OidcProfile, 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的CallbackFilterSecurityFilter 。 您可以看到SecurityFilter通过其configFactory init-param链接到SecurityConfigFactory类。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"version="3.1"><display-name>javaee-pac4j-demo</display-name><absolute-ordering/><filter-mapping><filter-name>corsFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><filter><filter-name>callbackFilter</filter-name><filter-class>org.pac4j.j2e.filter.CallbackFilter</filter-class><init-param><param-name>defaultUrl</param-name><param-value>/</param-value></init-param><init-param><param-name>renewSession</param-name><param-value>true</param-value></init-param><init-param><param-name>multiProfile</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>callbackFilter</filter-name><url-pattern>/callback</url-pattern><dispatcher>REQUEST</dispatcher></filter-mapping><filter><filter-name>OidcFilter</filter-name><filter-class>org.pac4j.j2e.filter.SecurityFilter</filter-class><init-param><param-name>configFactory</param-name><param-value>com.okta.developer.SecurityConfigFactory</param-value></init-param><init-param><param-name>clients</param-name><param-value>oidcClient,headerClient</param-value></init-param><init-param><param-name>authorizers</param-name><param-value>securityHeaders</param-value></init-param></filter><filter-mapping><filter-name>OidcFilter</filter-name><url-pattern>/*</url-pattern><dispatcher>REQUEST</dispatcher><dispatcher>FORWARD</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); // It's 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 xmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://java.sun.com/jsf/html"xmlns:f="http://java.sun.com/jsf/core"xmlns:ui="http://java.sun.com/jsf/facelets"template="/WEB-INF/template.xhtml"><ui:define name="title">Pac4J Java EE Demo - Protected Area</ui:define><ui:define name="content"><div class="ui-g"><div class="ui-g-12"><div class="ui-container"><h1>Protected Area</h1><p><h:link value="Back" outcome="/index"/></p></div><ui: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 xmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://java.sun.com/jsf/html"xmlns:f="http://java.sun.com/jsf/core"xmlns:ui="http://java.sun.com/jsf/facelets"><div class="ui-container"><p>Found  <h:outputText value="#{profileView.profiles.size()}"/> profiles.</p><h:panelGroup layout="block" rendered="#{profileView.profiles.size() > 0}"><p>First profile:  <h:outputText value="#{profileView.profile}"/></p></h:panelGroup></div><h:panelGroup layout="block" rendered="#{not empty profileView.profile}"><h2>Profile Details</h2><p><h:outputText value="Id: #{profileView.profile.id}"/></p><p><h:outputText value="Type Id: #{profileView.profile.typedId}"/></p><p><h:outputText value="Remembered: #{profileView.profile.remembered}"/></p><h3>Attributes (<h:outputText value="#{profileView.profile.attributes.size()}"/>)</h3><h:panelGroup layout="block" rendered="#{profileView.profile.attributes.size() > 0}"><ul><ui:repeat value="#{profileView.profile.attributes.keySet().toArray()}" var="attributeName"><li><h:outputText value="#{attributeName}"/>: <h:outputText value="#{profileView.profile.attributes.get(attributeName)}"/> </li></ui:repeat></ul></h:panelGroup><h3>Roles (<h:outputText value="#{profileView.profile.roles.size()}"/>)</h3><h:panelGroup layout="block" rendered="#{profileView.profile.roles.size() > 0}"><ul><ui:repeat value="#{profileView.profile.roles.toArray()}" var="role"><li><h:outputText value="#{role}"/></li></ui:repeat></ul></h:panelGroup><h3>Permissions (<h:outputText value="#{profileView.profile.permissions.size()}"/>)</h3><h:panelGroup layout="block" rendered="#{profileView.profile.permissions.size() > 0}"><ul><ui:repeat value="#{profileView.profile.permissions.toArray()}" var="permission"><li><h:outputText value="#{permission}"/></li></ui:repeat></ul></h:panelGroup></h:panelGroup>
</ui:composition>

oidc/index.xhtml模板使用WEB-INF/template.xhtml ,因此您也需要创建它。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://java.sun.com/jsf/html"xmlns:f="http://java.sun.com/jsf/core"xmlns:ui="http://java.sun.com/jsf/facelets"><h:head><f:facet name="first"><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/><meta name="apple-mobile-web-app-capable" content="yes" /></f:facet><title><ui:insert name="title">Pac4J Java EE Demo</ui:insert></title><ui:insert name="head"/></h:head><h:body styleClass="main-body"><div class="layout-wrapper"><div class="layout-main"><ui:insert name="content"/></div></div></h:body>
</html>

添加这些文件后,重建项目并重新启动TomEE。

mvn clean package tomee:run

导航到http://localhost:8080/oidc/index.jsf ,您将被重定向到Okta进行登录。 如果您初次尝试无法解决问题,请重新启动浏览器并使用隐身窗口。 您应该看到用户的个人资料信息。

Java REST API

http://localhost:3000尝试您的React客户端; 它也应该工作!

Java REST API

如果您想知道为什么不堆叠图像,那是因为我将React应用程序的BeerList.tsx的啤酒清单的JSX更改为内联。

<h2>Beer 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 TomEE,OpenEJB和Geronimo项目的创始人。 Apache,JCP EC,EE4J PMC,Jakarta EE WG,MicroProfile和Eclipse Board的成员。 首席执行官@Tomitribe 。

我问戴维何时会发布可用的Jakarta EE。

David:目前的主要重点是创建与Java EE 8兼容的Jakarta EE版本。我们希望在今年年底之前将其发布。 发布之后,我们将开始开发Jakarta EE 9并根据需要进行迭代。

Jakarta EE有一个工作组来决定平台的方向。

了解有关安全REST API,Java EE,Jakarta 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 Boot,OAuth 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.html

oidc

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/333103.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis实现CRUD操作

项目实现的功能 查询所有用户信息 通过Id查询用户信息 添加用户&#xff08;回显主键&#xff09; 修改用户信息 删除用户信息 通过用户名字模糊查询 一、引入依赖和工程结构 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http…

Linux系统下MySQL的导出数据语句SELECT … INTO OUTFILE的用法

文章目录关于参数 secure_file_priv按默认参数设定导出表数据到文本文件中自定义分隔符&#xff0c;将表数据导出到 txt 文件中自定义分隔符&#xff0c;将表数据导出到 csv 文件中导出数据时&#xff0c;提示“拒绝访问”总结确保 Linux 用户 mysql 对导出的目标目录拥有写入和…

java cxf_拥抱模块化Java平台:Java 10上的Apache CXF

java cxfJava 9版本终于将Project Jigsaw交付给大众已经过去了一年的时间。 这是一段漫长的旅程&#xff0c;但是在那里&#xff0c;所以发生了什么变化&#xff1f; 这是一个很好的问题&#xff0c;答案并不明显和直接。 总的来说&#xff0c; 拼图项目是一种颠覆性的变化&am…

MySQL数据字典

数据字典就类似于系统编目或花名册&#xff0c;它保存数据库服务器上的元数据信息&#xff08;数据库的整体属性信息&#xff09;。 元数据信息包括&#xff1a;数据库的属性信息、数据表的属性信息、字段的属性信息、视图的属性信息、用户信息、统计类信息等。 MySQL 保存元…

Mybatis映射文件SQL语句模糊查询,#和$的区别和注意事项

Mybatis映射文件SQL语句模糊查询 1. “%”#{value}"%" 在参数中不需要添加 %_ 推荐使用 2. ‘%${value}%’ 在参数中不需要添加 %_ 3. #{abc} 在参数中添加 %_ #和$的区别和注意事项 ${}直接拼接 &#xff0c;不会转换类型 如果是简单类型&#xff0c;必须写${…

启动php-fpm服务器_无服务器冷启动不是问题-这就是为什么(对于大多数应用程序)...

启动php-fpm服务器从无服务器开始时&#xff0c;您很快就会学习/听到有关函数冷启动的信息&#xff08;我相信无服务器云功能 API &#xff09;。 首次调用云功能时或长时间不调用后会发生冷启动。 基本上&#xff0c;服务器&#xff08;是的&#xff0c;有服务器&#xff01;&…

声明定义存储过程或者函数时,其中的 delimiter 关键字是干嘛的呢?

我们写 SQL 的时候&#xff0c;MySQL 怎么判断 SQL 是否已经结束了&#xff0c;可以去执行了&#xff1f; 需要一个结束符&#xff0c;当 MySQL 看到这个结束符的时候&#xff0c;表示可以执行前面的语句了&#xff0c;MySQL 默认以分号为结束符。 当我们创建存储过程或者自定…

istio api_Istio的网络API解释了

istio apiIstio 1.0版附带一个网络API&#xff0c;该API包含许多功能并涵盖了各种场景。 联网API在最近几个月中得到了发展&#xff0c;并且可能不会立即说明。 该API的概念和构建块是什么&#xff0c;以及如何使用各个Istio资源类型来通过我们的服务网格路由流量&#xff1f; …

MySQL命令之mysqldump的选项详解

文章目录--opt--skip-opt--add-drop-table--add-locks--skip-add-locks--allow-keywords--all-databases,-A--comments--skip-comments--compact--complete-insert,-c--compress,-C--events,-E--compatible--skip-disable-keys,-K--skip-add-drop-table--quick,-q--skip-quick-…

网关限流(令牌桶算法)

一、需求&#xff1a; 每个ip地址1秒内只能发送1次请求&#xff0c;多出来的请求返回429错误。 二、引入依赖 spring cloud gateway 默认使用redis的RateLimter限流算法来实现。所以我们要使用首先需要引入redis的依赖 <!--redis--> <dependency><groupId&g…

openapi_MicroProfile OpenAPI上的Swagger UI

openapiMicroProfile OpenApi为我们提供了一种使用OpenApi 3描述我们JAX-RS API的标准化方法。如果您以前使用过swagger-jaxrs和swagger- 批注 &#xff0c;由于OpenApi是基于Swagger构建的&#xff0c;因此您会感到非常熟悉。 2015年11月5日&#xff0c;SmartBear与3Scale&am…

MySQL的用户表(user)

文章目录字段 host 中的特殊值介绍user 字段中的特殊值介绍我们查看下这张表的字段 host 和 user&#xff0c;如下所示&#xff1a; mysql> select host,user from user; ----------------------------- | host | user | ----------------------------- |…

BCrypt管理员登录密码验证

一、简单入门测试 BCrypt不支持反运算&#xff0c;只支持密码校验 package com.william.test;import org.springframework.security.crypto.bcrypt.BCrypt;/*** author &#xff1a;lijunxuan* date &#xff1a;Created in 2019/7/11 11:34* description &#xff1a;* ver…

通用数据库管理工具_了解为什么这个直观的工具是您团队的通用团队管理工具...

通用数据库管理工具每个项目管理工具都试图做同样的工具性工作&#xff1a;保持团队联系&#xff0c;按任务执行和按时完成重大计划。 但是市场变得非常拥挤&#xff0c;并且有充分的理由-没有平台似乎对人们需要看到的东西以及应该如何显示这些信息具有正确的感觉&#xff0c;…

MySQL查询结果纵向输出标识符\G

\G 这个 G 是 group 的首字母&#xff0c;表示每行记录分组显示。 默认情况下&#xff0c;表数据的查询结果是横向输出的&#xff0c;如下所示&#xff1a; mysql> select * from student; ----------------------------------------------------------------------------…

用jackson转json_用Jackson编写大JSON文件

用jackson转json有时您需要将大量数据导出到JSON到文件中。 也许是“将所有数据导出到JSON”&#xff0c;或者是GDPR“可移植性的权利”&#xff0c;您实际上需要这样做。 与任何大型数据集一样&#xff0c;您不能只将其全部容纳在内存中并将其写入文件。 这需要一段时间&…

JWT 实现微服务鉴权

一、JWT JSON Web Token&#xff08;JWT&#xff09;是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。 一个JWT实际上就是一个字符串&#xff0c;它由三部分组成&#xff0c;头部、载荷与签名。 头部&#xff08;Header&#xff09; 头…

MySQL查看数据库系统正在运行的进程

在 MySQL 5.6 以前&#xff0c;我们通过 show processlist 命令查看系统中正在运行的所有进程&#xff1a; mysql> show processlist; ------------------------------------------------------------------------------------------------ | Id | User | Host …

oauth2.0授权码_OAUTH 2.0授权码授予

oauth2.0授权码OAuth 2.0提供了许多安全流程&#xff08;或授权类型&#xff09;&#xff0c;以允许一个应用程序访问另一个应用程序中的用户数据。 在此博客中&#xff0c;我们将介绍OAuth 2.0授权&#xff1a;授权代码授权。 首先&#xff0c;有许多定义&#xff1a; 客户…

JJWT签发与验证token

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License&#xff0c;版本2.0)&#xff0c;JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面&#xff0c;隐藏了它的大部分复杂性。 官方文档&#xff1a; https://github.com/jwtk/jjwt 一、…