# spring-security-gitee-experiment-4 **Repository Path**: chenbairui/spring-security-gitee-experiment4 ## Basic Information - **Project Name**: spring-security-gitee-experiment-4 - **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 #

东莞理工学院网络空间安全学院

####

课程名称:企业级开发框架专题                  学期:2020春季



实验名称基于Spring Security码云OAuth2认证实验序号
姓 名陈伯瑞学 号201741412102班 级17软卓1班
实验地点在家实验日期2020/05/14指导老师黎志雄
教师评语实验成绩 评阅教师
百分制
同组同学
### 一、 实验目的 1. 掌握使用Spring Security框架; 2. 掌握配置Spring Security的安全过滤链; 3. 掌握编写Spring Security单元测试; 4. 掌握创建接入码云的应用; 5. 掌握码云OAuth2认证基本流程; 6. 掌握使用码云API; 7. 了解使用模板引擎或前端框架制作用户登录界面。 ### 二、 实验环境 1. JDK 1.8或更高版本 2. Maven 3.6+ 3. IntelliJ IDEA ### 三、 实验任务 1. 创建接入码云的应用 ![创建应用](/img/1.png) /////////////////////////////////////////////////// /// 步骤一:创建接入码云的应用 /// 参考:https://gitee.com/api/v5/oauth_doc#/list-item-3 static final String CLIENT_ID = "ecb3da4acf74fa35ad5d31ca59c78260824ab934908a3bfa5738a8436af5e036"; static final String CLIENT_SECRET = "edaeb8d09dd56e3b0d7dfdf3b2ef2226ed973c8cc8c7d50c6d1cf94f47e88dba"; /////////////////////////////////////////////////// 2. 编写重定向过滤器的业务逻辑。 当用户访问/oauth2/gitee时,本重定向过滤器拦截请求,并将用户重定向到码云三方认证页面上 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 如果不是/oauth2/gitee请求,则继续下一个过滤器。 if (!request.getRequestURI().endsWith(DEFAULT_AUTHORIZATION_REQUEST_BASE_URI)) { filterChain.doFilter(request, response); return;// 执行完安全过滤器链后不执行后面的代码。 } // 应用通过 浏览器 或 Webview 将用户引导到码云三方认证页面上( GET请求 ) // 重定向地址:https://gitee.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code ////////////////////////////////////////////////////////////// /// 步骤二:编写重定向过滤器的业务逻辑。 /// 当用户访问/oauth2/gitee时,本重定向过滤器拦截请求,并将用户重定向到码云三方认证页面上。 ////////////////////////////////////////////////////////////// String url = UriComponentsBuilder.fromUriString(AUTHORIZE_URL) .buildAndExpand(CLIENT_ID, REDIRECT_URI).toString(); response.sendRedirect(url); } } 3. 使用码云access_token API向码云认证服务器发送post请求获取access_token private String getAccessToken(String code) { //////////////////////////////////////////////////// /// 步骤三:使用码云access_token API向码云认证服务器发送post请求获取access_token。 String url = UriComponentsBuilder.fromUriString(ACCESS_TOKEN_API_URI) .buildAndExpand(code, CLIENT_ID, REDIRECT_URI, CLIENT_SECRET).toString(); RequestEntity requestEntity = RequestEntity.post(URI.create(url)) .headers(httpHeaders -> httpHeaders.add("User-Agent", "Mozilla/5.8")) .build(); RestTemplate restTemplate = new RestTemplate(); ResponseEntity responseEntity = restTemplate.exchange(requestEntity, String.class); String body = responseEntity.getBody(); Map parseMap = new JacksonJsonParser(new ObjectMapper()).parseMap(body); String accessToken= (String) parseMap.get("access_token"); return accessToken; //////////////////////////////////////////////////// // 正确返回的access_token的json字符串: // {"access_token":"7282a1140867f6e3527f805af1950ea8","token_type":"bearer","expires_in":86400,"refresh_token":"0664cd3b66e36943b341285764a257ccfc7265a319dfcdd93c5f1bfbd4e023f1","scope":"user_info","created_at":1589124246} } 4. 使用码云API获取授权用户的资料 private Map getUserInfo(String accessToken) { //////////////////////////////////////////////////// /// 步骤四:使用码云API获取授权用户的资料。 /// 参考:https://gitee.com/api/v5/swagger#/getV5User String url = UriComponentsBuilder.fromUriString(USER_INFO_URI) .buildAndExpand(accessToken).toString(); RequestEntity requestEntity = RequestEntity.get(URI.create(url)) .headers(httpHeaders -> { httpHeaders.add("User-Agent", "Mozilla/5.8"); }) .build(); RestTemplate rest = new RestTemplate(); ResponseEntity responseEntity = rest.exchange(requestEntity, String.class); String body = responseEntity.getBody(); return new JacksonJsonParser(new ObjectMapper()).parseMap(body); //////////////////////////////////////////////////// } 5. 把自定义的两个Filter加进安全过滤链 @Override public void configure(H http) { //////////////////////////////////////////////////////////////// /// 步骤五:把自定义的两个Filter加进安全过滤链 /// 注意:不要加在SecurityContextPersistenceFilter前面就行。 //////////////////////////////////////////////////////////////// http.addFilterAfter(postProcess(new GiteeOAuth2LoginAuthenticationFilter()), HeaderWriterFilter.class) .addFilterAfter(postProcess(new GiteeOAuth2RedirectFilter()),HeaderWriterFilter.class); } 6. 把我们自定义的SecurityConfigurer应用到安全过滤链 @Override protected void configure(HttpSecurity http) throws Exception { // 不指定path,本安全过滤链会匹配所有请求。 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) //////////////////////////////////////////////// /// 步骤六:把我们自定义的SecurityConfigurer应用到安全过滤链 //////////////////////////////////////////////// .and() .apply(new GiteeOAuth2LoginConfigurer<>()) ; } 7. 改造/user接口,返回码云用户资料给前端;改造user.ftlh模板用于显示用户资料。 @GetMapping("/user") String userIndex(Model model) { //////////////////////////////////// /// 步骤七:改造/user接口,返回码云用户资料给前端;改造user.ftlh模板用于显示用户资料。 //////////////////////////////////// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); try{ Map userInfo = (Map) authentication.getPrincipal(); model.addAttribute("userInfo",userInfo); } catch (Exception e){ /*此处是本地登录*/ return "user"; } return "userGitee"; } 8. 编写单元测试。模拟一个登录用户,访问受保护的接口/test,断言接口的返回内容body部分是否一致。 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) class SpringSecurityGiteeExperimentApplicationTests { ///// Setting Up MockMvc and Spring Security. start @Autowired private WebApplicationContext context; private MockMvc mvc; // 在本类中,每次执行测试都先调用@BeforeEach注解的方法 @BeforeEach public void init() { mvc = MockMvcBuilders .webAppContextSetup(context) .apply(springSecurity()) .build(); } ///// Setting Up MockMvc and Spring Security. end /** * 测试限权接口 *

* 模拟一个登录用户,访问受保护的接口。 *

* @see WithMockUser * @see MockMvc#perform(RequestBuilder) * @see MockMvcRequestBuilders#get(String, Object...) */ @Test public void test() throws Exception { //////////////////////////////////////////// /// 步骤八:模拟一个登录用户,访问受保护的接口/test,断言接口的返回内容body部分是否一致。 //////////////////////////////////////////// mvc.perform(MockMvcRequestBuilders.get("/test")) .andExpect(status().isOk()) .andExpect(content().string("访问/test接口成功,你拥有USER权限")); } @Test public void test2() throws Exception { // 测试登录成功 mvc.perform(formLogin("/login").user("user").password("user")) .andExpect(unauthenticated()); } } 9. 运行效果: ![创建应用](/img/2.png)