# SpringBoot实验四 **Repository Path**: kevinsoft/spring_boot_experiment_4 ## Basic Information - **Project Name**: SpringBoot实验四 - **Description**: 实验四: 基于Spring Security码云OAuth2认证 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-05-15 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 实验四: 基于Spring Security码云OAuth2认证 ## 一、创建接入码云的应用 ```java static final String CLIENT_ID = "ce6e1e6009398cce61033ea9a44ead9ba7b952196b02c61d574d9cd80a87dc45"; static final String CLIENT_SECRET = "e32baf572fe58a0f3bd65e866ca0d930f45070c5931f9247b4532b8f01838905"; ``` ## 二、编写重定向过滤器的业务逻辑。 > 当没登录的用户访问路径为/oauth2/gitee的时候,重定向到码云登录页面,获取code ```java response.sendRedirect(String.format("https://gitee.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code", CLIENT_ID, REDIRECT_URI)); ``` ## 三、用码云access_token API向码云认证服务器发送post请求获取access_token。 > 用户登录成功后码云将用户重定向到/login/oauth2/code/gitee?code,用户访问过来即可获取code,使用code去码云换取access_token ```java URI uri = UriComponentsBuilder.fromUriString(String.format("https://gitee.com/oauth/token?grant_type=authorization_code&code=%s&client_id=%s&redirect_uri=%s&client_secret=%s", code, CLIENT_ID, REDIRECT_URI, CLIENT_SECRET)) .build(42); // header不加User-Agent就403 Forbidden RequestEntity requestEntity = RequestEntity.post(uri) .header("User-Agent", "spring-security-gitee-experiment") .build(); ResponseEntity response = rest.exchange(requestEntity, String.class); return response.getBody(); ``` ## 四、使用码云API获取授权用户的资料。 > 这一步是使用access_token去调用码云openapi获取用户资料,实验要求获取用户信息即可。所以我们只需要获取到用户名,再使用用户名去调用用户信息接口,在登录过程只记录用户名即可。 ```java try { Map map = objectMapper.readValue(accessToken, Map.class); String access_token = (String) map.get("access_token"); URI uri = UriComponentsBuilder.fromUriString(String.format("https://gitee.com/api/v5/user?access_token=%s", access_token)) .build(42); // header不加User-Agent就403 Forbidden RequestEntity requestEntity = RequestEntity.get(uri) .header("User-Agent", "spring-security-gitee-experiment") .build(); ResponseEntity response = rest.exchange(requestEntity, String.class); Map res = objectMapper.readValue(response.getBody(), Map.class); return new HashMap(res); } catch (Exception e) { e.printStackTrace(); } return null; ``` ## 五、把自定义的两个Filter加进安全过滤链 > 一定要把过滤器放在SecurityContextPersistenceFilter后面,因为自定义的过滤器没有判断是否登录成功,所以每次请求都会当成没登录处理,每次都会跳转登录。 ```java http.addFilterAfter(new GiteeOAuth2RedirectFilter(), SecurityContextPersistenceFilter.class); http.addFilterAfter(new GiteeOAuth2LoginAuthenticationFilter(), SecurityContextPersistenceFilter.class); ``` ## 六、把我们自定义的SecurityConfigurer应用到安全过滤链 > 使用HttpSecurity的apply方法加入自定义安全过滤链 ```java http .authorizeRequests() .antMatchers("/").permitAll()// 首页放行 .anyRequest().hasAnyAuthority("USER").and() .formLogin() .loginPage("/user/login_frontend").permitAll() .defaultSuccessUrl("/user").and() .logout() .logoutUrl("/user/logout").permitAll() .logoutSuccessUrl("/user/login_frontend?logout").and() // 自定义访问拒绝异常处理逻辑 .exceptionHandling().accessDeniedHandler(UserSecurityConfig::accessDeniedHandle).and() //////////////////////////////////////////////// /// 步骤六:把我们自定义的SecurityConfigurer应用到安全过滤链 //////////////////////////////////////////////// .apply(new GiteeOAuth2LoginConfigurer()) ; ``` ## 七、改造/user接口,返回码云用户资料给前端;改造user.ftlh模板用于显示用户资料。 ```java Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = (User) authentication.getPrincipal(); URI uri = UriComponentsBuilder.fromUriString("https://gitee.com/api/v5/users/" + user.getUsername()) .build(42); RequestEntity requestEntity = RequestEntity.get(uri) .header("User-Agent", "spring-security-gitee-experiment") .build(); ResponseEntity response = rest.exchange(requestEntity, String.class); model.addAttribute("userInfo", objectMapper.readValue(response.getBody(), Map.class)); model.addAttribute("securityDetails", authentication.getDetails()); return "user"; ``` 下面是运行情况: > 首页 ![image-20200515041018720](https://gitee.com/kevinsoft/spring_boot_experiment_4/raw/master/images/image-20200515041018720.png) > 登录页 ![image-20200515041110606](https://gitee.com/kevinsoft/spring_boot_experiment_4/raw/master/images/image-20200515041110606.png) ![image-20200515041125278](https://gitee.com/kevinsoft/spring_boot_experiment_4/raw/master/images/image-20200515041125278.png) > 个人信息页 ![image-20200515041143150](https://gitee.com/kevinsoft/spring_boot_experiment_4/raw/master/images/image-20200515041143150.png) ![image-20200515041203384](https://gitee.com/kevinsoft/spring_boot_experiment_4/raw/master/images/image-20200515041203384.png) ## 八、模拟一个登录用户,访问受保护的接口/test,断言接口的返回内容body部分是否一致。 > @WithMockUser要加USER权限 ``` @Test @WithMockUser(authorities = "USER") public void test() throws Exception { //////////////////////////////////////////// /// 步骤八:模拟一个登录用户,访问受保护的接口/test,断言接口的返回内容body部分是否一致。 //////////////////////////////////////////// mvc.perform(MockMvcRequestBuilders.get("/test")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string("访问/test接口成功,你拥有USER权限")); } ```