使用Spring框架各個組件實現一個在線聊天網頁,當有用戶鏈接WebSocket,服務器監聽到用戶鏈接會使用Stomp推送最新用戶列表,有用戶斷開刷新在線列表,實時推送用戶聊天信息。引入Jetty服務器,直接嵌入整個工程能夠脫離Java Web容器獨立運行,使用插件打包成一個jar文件,就像Spring Boot同樣運行,部署。html
pom.xml 依賴
前端
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<jetty.version>9.4.8.v20171121</jetty.version>
<spring.version>5.0.4.RELEASE</spring.version>
<jackson.version>2.9.4</jackson.version>
<lombok.version>1.16.18</lombok.version>
<dbh2.version>1.4.196</dbh2.version>
<jcl.slf4j.version>1.7.25</jcl.slf4j.version>
<spring.security.version>5.0.3.RELEASE</spring.security.version>
<logback.version>1.2.3</logback.version>
<activemq.version>5.15.0</activemq.version>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
<!-- 添加websocket 依賴否則會出現 java.lang.IllegalStateException: No suitable default
RequestUpgradeStrategy found -->
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-api</artifactId>
<version>${jetty.version}</version>
</dependency>
<!--spring mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-messaging</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${dbh2.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${jcl.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<version>0.7.2.RELEASE</version>
</dependency>
</dependencies>
複製代碼
@Bean //內存模式
public DataSource dataSource(){
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase build = builder.setType(EmbeddedDatabaseType.H2)
.addScript("db/sql/create-db.sql") //每次建立數據源都會執行腳本
.addScript("db/sql/insert-data.sql")
.build();
return build;
}
複製代碼
這種方式是利用Spring 內置的嵌入式數據庫的數據源模板,建立的數據源,比較簡單,可是這種方式不支持定製,數據只能保存在內存中,項目重啓數據就會丟失了。java
設置數據保存到硬盤react
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUsername("embedded");
dataSource.setPassword("embedded");
dataSource.setUrl("jdbc:h2:file:./data;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;");
return dataSource;
}
複製代碼
若是你還想每次建立數據源執行初始化sql,使用org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
裝載sql 腳本用於初始化或清理數據庫git
@Bean
public ResourceDatabasePopulator databasePopulator() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(schema);
populator.addScripts(data);
populator.setContinueOnError(true);
return populator;
}
複製代碼
設置DatabasePopulator 對象,用戶數據源啓動或者消耗的時候執行腳本github
@Bean
public DataSourceInitializer initializer() {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDatabasePopulator(databasePopulator());
initializer.setDataSource(dataSource());
return initializer;
}
複製代碼
啓用H2 web Consoleweb
@Bean(initMethod = "start",destroyMethod = "stop")
public Server DatasourcesManager() throws SQLException {
return Server.createWebServer("-web","-webAllowOthers","-webPort","8082");
}
複製代碼
瀏覽器打開 http://localhost:8082
訪問H2 控制檯ajax
設置事務管理器spring
@Bean
public PlatformTransactionManager transactionManager() {
PlatformTransactionManager manager = new DataSourceTransactionManager(dataSource());
return manager;
}
}
複製代碼
到這裏,嵌入H2數據庫配置基本已經設置完成了sql
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.ting.spring.controller", //基包路徑設置
includeFilters = @ComponentScan.Filter(value =
{ControllerAdvice.class,Controller.class})) //只掃描MVC controll的註解
public class WebMvcConfiguration implements WebMvcConfigurer {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//添加靜態路徑映射
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
}
複製代碼
由於Spring 註解掃描只能註冊一個類, 使用@Import
引入其餘的配置類
@Configuration
@ComponentScan(basePackages = "org.ting.spring",
excludeFilters = {@ComponentScan.Filter(value = {Controller.class,ControllerAdvice.class})})
@Import({WebMvcConfiguration.class}) //引入Spring MVC配置類
public class WebRootConfiguration {
@Autowired
private DataSource dataSource;
@Bean
public JdbcTemplate jdbcTemplate(){
JdbcTemplate template = new JdbcTemplate(dataSource);
return template;
}
}
複製代碼
使用Spring AnnotationConfigWebApplicationContext
啓動註解掃描,註冊建立bean將WebApplicationContext
,在將對象傳給DispatcherServlet
public class JettyEmbedServer {
private final static int DEFAULT_PORT = 9999;
private final static String DEFAULT_CONTEXT_PATH = "/";
private final static String MAPPING_URL = "/*";
public static void main(String[] args) throws Exception {
Server server = new Server(DEFAULT_PORT);
JettyEmbedServer helloServer = new JettyEmbedServer();
server.setHandler(helloServer.servletContextHandler());
server.start();
server.join();
}
private ServletContextHandler servletContextHandler() {
WebApplicationContext context = webApplicationContext();
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletContextHandler.setContextPath(DEFAULT_CONTEXT_PATH);
ServletHolder servletHolder = new ServletHolder(new DispatcherServlet(context));
servletHolder.setAsyncSupported(true);
servletContextHandler.addServlet(servletHolder, MAPPING_URL);
return servletContextHandler;
}
private WebApplicationContext webApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(WebRootConfiguration.class);
return context;
}
複製代碼
默認Spring Security攔截請求,登陸失敗,登陸成功都是頁面跳轉的方式,咱們但願ajax請求的時候,不管是被攔截了,或者登陸失敗,成功均可以返回json格式數據,由前端人員來處理。 根據HttpRequestServlet
請求頭 X-Requested-With
是否等於XMLHttpRequest
判斷是不是ajax。
public class RespnonseJson {
public static void jsonType(HttpServletResponse response) {
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("utf-8");
}
public static boolean ajaxRequest(HttpServletRequest request){
String header = request.getHeader("X-Requested-With");
return ! StringUtils.isEmpty(header) && header.equals("XMLHttpRequest");
}
public static boolean matchURL(String url) {
Pattern compile = Pattern.compile("^/api/.+");
return compile.matcher(url).matches();
}
}
複製代碼
登陸認證處理器
public class RestAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
/**
* @param loginFormUrl URL where the login page can be found. Should either be
* relative to the web-app context path (include a leading {@code /}) or an absolute
* URL.
*/
public RestAuthenticationEntryPoint(String loginFormUrl) {
super(loginFormUrl);
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
String uri = request.getRequestURI();
if (matchURL(uri)) { // /api 都是ajax 請求
jsonType(response);
response.getWriter().println(getErr(authException.getMessage()));
}else if (ajaxRequest(request)){
jsonType(response);
response.getWriter().println(getErr(authException.getMessage()));
}else super.commence(request,response,authException);
}
private String getErr(String description) throws JsonProcessingException {
Result result = Result.error(Result.HTTP_FORBIDDEN, description);
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(result);
}
}
複製代碼
登陸成功處理
public class RestAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String uri = request.getRequestURI();
if (matchURL(uri)){
jsonType(response);
String value = loginSuccess();
response.getWriter().println(value);
}else if (ajaxRequest(request)){
jsonType(response);
String success = loginSuccess();
response.getWriter().println(success);
}else super.onAuthenticationSuccess(request,response,authentication);
}
private String loginSuccess() throws JsonProcessingException {
Result success = Result.success("sign on success go to next!");
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(success);
}
}
複製代碼
登陸失敗處理
public class RestAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if (ajaxRequest(request)){
jsonType(response);
String err = getErr(exception.getMessage());
response.getWriter().println(err);
}else super.onAuthenticationFailure(request,response,exception);
}
public String getErr(String description) throws JsonProcessingException {
Result result = Result.error(Result.HTTP_AUTH_FAILURE, description);
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(result);
}
}
複製代碼
我在網上搜索ajax 認證錯誤,不少博客是這樣寫的
response.sendError(500, "Authentication failed");
這個錯誤會被Jetty 錯誤頁面捕獲,擾亂返回JSON數據,這個細節要注意下
註冊Handler
@Bean
public AuthenticationEntryPoint entryPoint() {
RestAuthenticationEntryPoint entryPoint = new RestAuthenticationEntryPoint("/static/html/login.html");
return entryPoint;
}
@Bean
public SimpleUrlAuthenticationSuccessHandler successHandler() {
RestAuthSuccessHandler successHandler = new RestAuthSuccessHandler();
return successHandler;
}
@Bean
public SimpleUrlAuthenticationFailureHandler failureHandler() {
RestAuthFailureHandler failureHandler = new RestAuthFailureHandler();
return failureHandler;
}
複製代碼
配置url 認證
@Bean
public SessionRegistry sessionManager() {
return new SessionRegistryImpl();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(entryPoint())
.and()
.authorizeRequests()
.antMatchers("/static/html/jetty-chat.html",
"/api/user/online", "/api/user/loginuser")
.authenticated() //設置須要認證才能夠請求的接口
.and()
.formLogin()
.successHandler(successHandler()) //登陸成功處理
.failureHandler(failureHandler()) //登陸失敗處理
.loginPage("/static/html/login.html") //登陸頁面
.loginProcessingUrl("/auth/login") //登陸表單url
.defaultSuccessUrl("/static/html/jetty-chat.html") //成功跳轉url
.permitAll()
.and().csrf().disable()//禁用csrf 由於沒有使用模板引擎
.sessionManagement().maximumSessions(1) //設置同一個帳戶,同時在線次數
.sessionRegistry(sessionManager()) // 設置Session 管理器,
.expiredUrl("/static/html/login.html") //session 失效後,跳轉url
.maxSessionsPreventsLogin(false) //設置true,達到session 最大登陸次數後,後面的帳戶都會登陸失敗,false 頂號 前面登陸帳戶會被後面頂下線
;
//註銷帳戶,跳轉到登陸頁面
http.logout().logoutUrl("/logout").logoutSuccessUrl("/static/html/login.html");
複製代碼
在配置類添加@EnableWebSecurity
,在掃描類上引入Spring Security配置,大功告成了,並無!Spring Security 是使用Filter來處理一些認證請求,須要咱們在Jetty中手動註冊攔截器
//手動註冊攔截器,讓Spring Security 生效
FilterHolder filterHolder = new FilterHolder(new DelegatingFilterProxy("springSecurityFilterChain"));
servletContextHandler.addFilter(filterHolder, MAPPING_URL, null);
servletContextHandler.addEventListener(new ContextLoaderListener(context));
servletContextHandler.addEventListener(new HttpSessionEventPublisher()); //使用security session 監聽器 限制只容許一個用戶登陸
複製代碼
@Configuration
@EnableWebSocketMessageBroker
@ComponentScan(basePackages = "org.ting.spring.stomp.message")
@Slf4j
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
//設置鏈接的端點路徑
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("endpoint").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 定義了兩個客戶端訂閱地址的前綴信息,也就是客戶端接收服務端發送消息的前綴信息
registry.enableSimpleBroker("/topic", "/queue");
// 定義了服務端接收地址的前綴,也即客戶端給服務端發消息的地址前綴
registry.setApplicationDestinationPrefixes("/app");
//使用客戶端一對一通訊
registry.setUserDestinationPrefix("/user");
registry.setPathMatcher(new AntPathMatcher("."));
}
}
複製代碼
配置stomp 頻道認證
@Configuration
public class SocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages.simpDestMatchers("/user/**").authenticated()//認證全部user 連接
.anyMessage().permitAll();
}
//容許跨域 否則會出現 Could not verify the provided CSRF token because your session was not found 異常
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
複製代碼
信息處理
@Controller
@Slf4j
public class StompController {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@MessageExceptionHandler
@SendToUser("/queue.errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}
@MessageMapping("receive.messgae")
public void forwardMsg(ChatMessage message){
log.info("message : {}",message);
message.setLocalDateTime(LocalDateTime.now());
messagingTemplate.convertAndSendToUser(message.getTargetUser().getEmail()
,"queue.notification",message);
}
}
複製代碼
@MessageMapping
做用與@RequestMapping
功能差很少用於匹配url 更多Spring WebSocket 官方文檔查看
咱們使用一個集合來保存鏈接上的用戶,使用鏈接,斷開監聽器來修改集合的列表,並將集合的數據發佈到頻道上。
websocket 斷開鏈接監聽器
@Component
@Slf4j
public class WebSocketDisconnectListener implements ApplicationListener<SessionDisconnectEvent> {
@Autowired
private UserService userService;
@Autowired
private SimpMessagingTemplate messageTemplate;
@Override
public void onApplicationEvent(SessionDisconnectEvent event) {
Principal principal = event.getUser();
log.info("client sessionId : {} name : {} disconnect ....",event.getSessionId(),principal.getName());
if (principal != null){ //已經認證過的用戶
User user = userService.findByEmail(principal.getName());
Online.remove(user);
messageTemplate.convertAndSend("/topic/user.list",Online.onlineUsers());
}
}
}
複製代碼
註冊鏈接websocket 監聽器
@Component
@Slf4j
public class WebSocketSessionConnectEvent implements ApplicationListener<SessionConnectEvent>{
@Autowired
private SimpMessagingTemplate messageTemplate;
@Autowired
private UserService userService;
@Override
public void onApplicationEvent(SessionConnectEvent event) {
Principal principal = event.getUser();
log.info("client name: {} connect.....",principal.getName());
if (principal != null){
User user = userService.findByEmail(principal.getName());
Online.add(user);
messageTemplate.convertAndSend("/topic/user.list",Online.onlineUsers());
}
}
}
複製代碼
保存在線列表
public class Online {
private static Map<String,User> maps = new ConcurrentHashMap<>();
public static void add(User user){
maps.put(user.getEmail(),user);
}
public static void remove(User user){
maps.remove(user.getEmail());
}
public static Collection<User> onlineUsers(){
return maps.values();
}
}
複製代碼
手動配置ClientRegistrationRepository 設置client-id
,client-secret
,redirect-uri-template
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(githubClientRegstrationRepository()
,googleClientRegistrionRepository());
}
public ClientRegistration githubClientRegstrationRepository(){
return CommonOAuth2Provider.GITHUB.getBuilder("github")
.clientId(env.getProperty("registration.github.client-id"))
.clientSecret(env.getProperty("registration.github.client-secret"))
.redirectUriTemplate(env.getProperty("registration.github.redirect-uri-template"))
.build();
}
public ClientRegistration googleClientRegistrionRepository(){
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId(env.getProperty("registration.google.client-id"))
.clientSecret(env.getProperty("registration.google.client-secret"))
.redirectUriTemplate(env.getProperty("registration.google.redirect-uri-template"))
.scope( "profile", "email")
.build();
}
@Bean
public OAuth2AuthorizedClientService authorizedClientService() {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository());
}
複製代碼
咱們使用github,google OAuth2 受權登陸的帳戶,登陸經過後保存起來,則需求繼承DefaultOAuth2UserService
@Service
@Slf4j
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Autowired
private UserService userService;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
try {
oAuth2User = processOAuth2User(oAuth2User,userRequest);
} catch (Exception e) {
log.error("processOAuth2User error {}",e);
}
return oAuth2User;
}
private OAuth2User processOAuth2User(OAuth2User oAuth2User,OAuth2UserRequest userRequest) {
String clientId = userRequest.getClientRegistration().getRegistrationId();
if (clientId.equalsIgnoreCase("github")) {
Map<String, Object> map = oAuth2User.getAttributes();
String login = map.get("login")+"_oauth_github";
String name = (String) map.get("name");
String avatarUrl = (String) map.get("avatar_url");
User user = userService.findByEmail(login);
if (user == null) {
user = new User();
user.setUsername(name);
user.setEmail(login);
user.setAvatar(avatarUrl);
user.setPassword("123456");
userService.insert(user);
}else {
user.setUsername(name);
user.setAvatar(avatarUrl);
userService.update(user);
}
return UserPrincipal.create(user, oAuth2User.getAttributes());
}else if (clientId.equalsIgnoreCase("google")){
Map<String, Object> result = oAuth2User.getAttributes();
String email = result.get("email")+"_oauth_google";
String username = (String) result.get("name");
String imgUrl = (String) result.get("picture");
User user = userService.findByEmail(email);
if (user == null){
user = new User();
user.setEmail(email);
user.setPassword("123456");
user.setAvatar(imgUrl);
user.setUsername(username);
userService.insert(user);
}else {
user.setUsername(username);
user.setAvatar(imgUrl);
userService.update(user);
}
return UserPrincipal.create(user,oAuth2User.getAttributes());
}
return null;
}
}
複製代碼
重寫UserDetails
public class UserPrincipal implements OAuth2User,UserDetails {
private long id;
private String name;
private String password;
private boolean enable;
private Collection<? extends GrantedAuthority> authorities;
private Map<String,Object> attributes;
UserPrincipal(long id,String name,String password,boolean enable,Collection<? extends GrantedAuthority> authorities){
this.id = id;
this.name = name;
this.password = password;
this.authorities = authorities;
this.enable = enable;
}
public static UserPrincipal create(User user){
return new UserPrincipal(user.getId(),user.getEmail()
,user.getPassword(),user.isEnable(),Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
}
public static UserPrincipal create(User user, Map<String, Object> attributes) {
UserPrincipal userPrincipal = UserPrincipal.create(user);
userPrincipal.attributes = attributes;
return userPrincipal;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return name;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enable;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}
@Override
public String getName() {
return String.valueOf(this.id);
}
}
複製代碼
設置Spring Security OAuth2 Client
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomOAuth2UserService customOAuth2UserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService())
.userInfoEndpoint()
.userService(customOAuth2UserService)
.and()
.defaultSuccessUrl("/static/html/jetty-chat.html");
}
}
複製代碼
默認受權端點,點擊後直接重定向到受權服務器的登陸頁面,Spring 默認是:oauth2/authorization/{clientId}
默認受權成功跳轉url: /login/oauth2/code/{clientId}
這個項目參考的教程: www.baeldung.com/spring-secu… www.callicoder.com/spring-boot…
這個教程只展現了一部分的代碼,想查看完整的項目代碼,能夠去github: spring-stomp-security-webflux-embedded-jetty查看