美文网首页Java 杂谈SpringFramework
Spring MVC实现Spring Security,Spri

Spring MVC实现Spring Security,Spri

作者: 神易风 | 来源:发表于2019-02-25 18:28 被阅读0次

    使用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}

    这个项目参考的教程:
    https://www.baeldung.com/spring-security-5-oauth2-login
    https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-1/

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

    相关文章

      网友评论

        本文标题:Spring MVC实现Spring Security,Spri

        本文链接:https://www.haomeiwen.com/subject/idgvyqtx.html