Spring MVC实现Spring Security,Spring Stomp websocket Jetty嵌入式运行

栏目: Java · 发布时间: 6年前

内容简介:使用Spring框架各个组件实现一个在线聊天网页,当有用户连接WebSocket,服务器监听到用户连接会使用Stomp推送最新用户列表,有用户断开刷新在线列表,实时推送用户聊天信息。引入Jetty服务器,直接嵌入整个工程可以脱离Java Web容器独立运行,使用插件打包成一个jar文件,就像Spring Boot一样运行,部署。这种方式是利用Spring 内置的嵌入式数据库的数据源模板,创建的数据源,比较简单,但是这种方式不支持定制,数据只能保存在内存中,项目重启数据就会丢失了。

使用Spring框架各个组件实现一个在线聊天网页,当有用户连接WebSocket,服务器监听到用户连接会使用Stomp推送最新用户列表,有用户断开刷新在线列表,实时推送用户聊天信息。引入Jetty服务器,直接嵌入整个工程可以脱离Java Web容器独立运行,使用插件打包成一个jar文件,就像Spring Boot一样运行,部署。

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>

1. 配置H2 嵌入式数据库

@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 内置的嵌入式数据库的数据源模板,创建的数据源,比较简单,但是这种方式不支持定制,数据只能保存在内存中,项目重启数据就会丢失了。

设置数据保存到硬盘

@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 脚本用于初始化或清理数据库

@Bean
    public ResourceDatabasePopulator databasePopulator() {
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(schema);
        populator.addScripts(data);
        populator.setContinueOnError(true);
        return populator;
    }

设置 DatabasePopulator 对象,用户数据源启动或者消耗的时候执行脚本

@Bean
    public DataSourceInitializer initializer() {
        DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDatabasePopulator(databasePopulator());
        initializer.setDataSource(dataSource());
        return initializer;
    }

启用H2 web Console

@Bean(initMethod = "start",destroyMethod = "stop")
    public Server DatasourcesManager() throws SQLException {
        return Server.createWebServer("-web","-webAllowOthers","-webPort","8082");
    }

浏览器打开 http://localhost:8082 访问H2 控制台

设置事务管理器

@Bean
    public PlatformTransactionManager transactionManager() {
        PlatformTransactionManager manager = new DataSourceTransactionManager(dataSource());
        return manager;
    }
}

到这里,嵌入H2数据库配置基本已经设置完成了

2. Spring MVC配置

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/");
    }
}

3. Jetty嵌入式服务

因为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;
    }

3. 配置Spring Security

默认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 监听器 限制只允许一个用户登录

4. 配置WebSocketStompConfig

@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();
    }

}

4. Spring Security OAuth2 Client 配置

手动配置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}

这个教程只展示了一部分的代码,想查看完整的项目代码,可以去 github: spring-stomp-security-webflux-embedded-jetty 查看


以上所述就是小编给大家介绍的《Spring MVC实现Spring Security,Spring Stomp websocket Jetty嵌入式运行》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Analytics 2.0

Web Analytics 2.0

Avinash Kaushik / Sybex / 2009-10-26 / USD 39.99

The bestselling book Web Analytics: An Hour A Day was the first book in the analytics space to move beyond clickstream analysis. Web Analytics 2.0 will significantly evolve the approaches from the fir......一起来看看 《Web Analytics 2.0》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具