文章目录
- 1.spring security入门
-
- 1.创建项目,引入依赖
- 2.security的其他配置方式
-
- 2.2基于内存
- 2.3HttpSecurity(入门配置此文件)
- 2.4多个HttpSecurity的配置(复杂业务场景下)—security
- 2.5配置方法安全
- 2.6基于数据库的认证(基础入门)—–sercuty-db/sercurity-dy
-
- 1.定义实体类user(继承UserDetails)和role
- 2.配置SecurityConfig
- 3.动态权限配置的Security写法
-
- 1. 1.先定义MyFilter实现FilterInvocationSecurityMetadataSource
- 3.1SecurityConfig的写法
- 2.7spring security整合oauth2—oauth2
-
- 2.7.1配置资源服务器 ResourceServerConfig
- 2.7.2授权服务器 AuthorizationServerConfig
- 2.7.3security配置文件
- 2.7.4 MyFilter
- 2.7.5 MyAccessDecisionManager
- 2.7.6 postman测试 -请求token
- 2.8spring security支持json登录—security-dy/security-db/oauth2
-
- 2.8.1重写UsernamePasswordAuthenticationFilter方法
- 2.8.2注入重写的方法
- 2.8.3配置登录成功后的转跳和失败后转跳
- 2.9spring security整合jwt –jwt-demo
-
- 2.9.1 JwtLoginFilter _登录的时候给用户token_
- 2.9.2. _访问系统的时候效验token_
- 2.9.3增加security的相关配置 configure_(_HttpSecurity http_) _
- 2.10 oauth2 整合jwt+swagger —swcurity-swagger/ 松哥例子swagger-jwt
-
- 2.10.1.AccessTokenConfig
- 2.10.2AuthorizationServer
- 2.10.3ResourceServerConfig
- 2.10.4 GlobalCorsConfiguration —支持跨域
- 2.11configure其他配置
1.spring security入门
decurity-demo.sql 数据库验证的文件
密码是123或者123456/ 可以使用 new BCryptPasswordEncoder_().encode(“123”)_;加密出来
模板项目地址https://gitee.com/find_me/java-findme
1.创建项目,引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
引入依赖后,项目中所有的接口就会被保护起来,项目默认用户名为user,密码为启动后随即生成的,在控制台打印的密码
2.security的其他配置方式
spring:security:user: name: find mepassword: 123456roles: admin
2.2基于内存
当然,开发者也可以自定义类继承自WebSecurityConfigurerAdapter ,进而实现对 Spring Security
更多的自定义配置,例如基于内存的认证,配置方式如下:
@Configuration public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { // 密码不加密的配置 ,过时的方法@Bean //PasswordEncoder passwordEncoder () { //return NoOpPasswordEncoder. getlnstance () ; // }// 密码需要加密@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}//基于内存的配置@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("javaboy").password("$2a$10$KlbLLUVVyvP0/vuhlabv0upg/ALdCrVEhUJOJDEND5ZtZJ.nGfVCm").roles("admin").and().withUser("findme").password("$2a$10$KlbLLUVVyvP0/vuhlabv0upg/ALdCrVEhUJOJDEND5ZtZJ.nGfVCm").roles("user");}
代码解释:
·自定义 MyWebSecurityConfig 继承自 WebSecurityConfigurerAdapter ,并重写configure(AuthenticationManagerBuilder auth)方法,在该方法中配直两个用户,一个用户名是
admin,密码123 ,具备两个角色 ADMIN USER 另一个用户名是 findme,密码是 123 ,具备一个角色USER
Spring Security 本是 0.6 Spring Security 中引入了 多种密码加密方式,开发者必须指定其中一种, NoOpPasswordEncoder ,即不对密码进行加密。
注意:基于配置和内存的方式在配置角色的时候不需要添加"ROLE_".基于数据库的需要
2.3HttpSecurity(入门配置此文件)
根据实际情况进行角色配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate ObjectMapper objectMapper;@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 配置登录的账号@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("javaboy").password("$2a$10$KlbLLUVVyvP0/vuhlabv0upg/ALdCrVEhUJOJDEND5ZtZJ.nGfVCm").roles("admin").and().withUser("findme").password("$2a$10$KlbLLUVVyvP0/vuhlabv0upg/ALdCrVEhUJOJDEND5ZtZJ.nGfVCm").roles("user");}//基本的httpSecurity配置// 1.请求路径角色// 2.配置表单登录// 3.配置登录成功,失败或者登出的处理方式// authentication里面存放登录成功后的信息// {// "password": null, 密码// "username": "javaboy",// "authorities": [ 具备的角色// {// "authority": "ROLE_admin" // }// ],// "accountNonExpired": true, 账户没有过期,// "accountNonLocked": true, 账户没有锁定// "credentialsNonExpired": true, 密码没有过期// "enabled": true 账户可用// },@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//开启配置.antMatchers("/admin/**").hasRole("admin")//路径符合这个需要admin角色
// .antMatchers("user/**").hasAnyRole("admin", "user")//路径符合这个,需要这两个的任意一个.antMatchers("/user/**").access("hasAnyRole('user','admin')").anyRequest().authenticated()//其他请求,登录后就可以访问.and().formLogin()//表单登录.loginProcessingUrl("/doLogin")//处理登录请求的地址.loginPage("/login")//配置登录页面(实际上还是一个请求地址) 前后端分类不存在这种页面 这里还是访问的应该请求,会根据这请求去返回登录页面.usernameParameter("uname")//修改登录的key.passwordParameter("passwd")//修改登录的key.successHandler(new AuthenticationSuccessHandler() { //登录成功的处理@Override// authentication保存了登录成功后的信息public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", authentication.getPrincipal());out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}}).failureHandler(new AuthenticationFailureHandler() {//登录失败的处理@Override // e登录失败的异常public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,AuthenticationException e) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 401);if (e instanceof LockedException) {map.put("msg", "账户被锁定,登录失败!");} else if (e instanceof BadCredentialsException) {map.put("msg", "用户名或密码输入错误,登录失败!");} else if (e instanceof DisabledException) {map.put("msg", "账户被禁用,登录失败!");} else if (e instanceof AccountExpiredException) {map.put("msg", "账户过期,登录失败!");} else if (e instanceof CredentialsExpiredException) {map.put("msg", "密码过期,登录失败!");} else {map.put("msg", "登录失败!");}out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}}).permitAll().and().logout().logoutUrl("/logout") //配置注销请求的地址.logoutSuccessHandler(new LogoutSuccessHandler() {//注销成功后的回调@Overridepublic void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp,Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", "注销登录成功!");out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}}).and().csrf().disable();/*** 权限不足处理*/http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);Map<String, Object> failureMap = new HashMap<>();failureMap.put("code", 403);failureMap.put("msg", "权限不足!");httpServletResponse.getWriter().println(objectMapper.writeValueAsString(failureMap));}});
// /**
// * 未登陆处理
// */
// http.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
// @Override
// public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
//
//
// httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//
// Map<String, Object> failureMap = new HashMap<>();
// failureMap.put("code", 401);
// failureMap.put("msg", "请先登录!");
//
// httpServletResponse.getWriter().println(objectMapper.writeValueAsString(failureMap));
//
// }
// });}
}
2.4多个HttpSecurity的配置(复杂业务场景下)—security
参考项目 spring-security-me/security
采用静态内部类的方式 采用order设置优先级,数字越小,优先级越大
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true) //开启方法安全
public class MultiHttpSecurityConfig {@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Autowiredprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("javaboy").password("$2a$10$G3kVAJHvmRrr6sOj.j4xpO2Dsxl5EG8rHycPHFWyi9UMIhtdSH15u").roles("admin").and().withUser("").password("$2a$10$kWjG2GxWhm/2tN2ZBpi7bexXjUneIKFxIAaMYJzY7WcziZLCD4PZS").roles("user");}@Configuration@Order(1)public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{@Overrideprotected void configure(HttpSecurity http) throws Exception {http.antMatcher("/admin/**").authorizeRequests().anyRequest().hasAnyRole("admin");//只有这种格式的路径才会去要求角色}}@Configurationpublic static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginProcessingUrl("/doLogin").permitAll().and().csrf().disable();}}
}
2.5配置方法安全
在Security上面加上
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true) //开启方法安全
然后在方法上加PreAuthorize注解
@Service
public class MethodService {@PreAuthorize("hasRole('admin')")// 这个方法需要admin角色public String admin() {return "hello admin";}@Secured("ROLE_user") //需要user角色public String user() {return "hello user";}@PreAuthorize("hasAnyRole('admin','user')")//需要两者之一public String hello() {return "hello hello";}
}
2.6基于数据库的认证(基础入门)—–sercuty-db/sercurity-dy
权限模型
权限实际开发模型
1.定义实体类user(继承UserDetails)和role
security-demo.sql
public class User implements UserDetails {private Integer id;private String username;private String password;private Boolean enabled;private Boolean locked;private List<Role> roles;public List<Role> getRoles() {return roles;}public void setRoles(List<Role> roles) {this.roles = roles;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}// 返回用户所有的角色@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> authorities = new ArrayList<>();// 重新整理一下 角色认证要以ROLE_开始 数据库没有就要在这里手动拼接for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getName()));}return authorities;}@Overridepublic String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String getUsername() {return username;}// 账户是否未过期 目前数据库没有就直说true@Overridepublic boolean isAccountNonExpired() {return true;}// 账户是否未锁定@Overridepublic boolean isAccountNonLocked() {return !locked;}// 密码是否未过期@Overridepublic boolean isCredentialsNonExpired() {return true;}// 是否可用@Overridepublic boolean isEnabled() {return enabled;}public void setUsername(String username) {this.username = username;}public void setEnabled(Boolean enabled) {this.enabled = enabled;}public void setLocked(Boolean locked) {this.locked = locked;}
}
2.配置SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 此处去调用数据库的或者采用静态数据@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}@Overrideprotected void configure(HttpSecurity http) throws Exception {//对于不同的路径要不同的角色 静态配置http.authorizeRequests().antMatchers("/dba/**").hasRole("dba").antMatchers("/admin/**").hasRole("admin").antMatchers("/user/**").hasRole("user").anyRequest().authenticated().and().formLogin().permitAll().and().csrf().disable();}// 角色继承的bean@BeanRoleHierarchy roleHierarchy() {RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();// 以前String hierarchy = "dba > admin admin > user";// 现在String hierarchy = "dba > admin \n admin > user";roleHierarchy.setHierarchy(hierarchy);return roleHierarchy;}
3.动态权限配置的Security写法
1. 1.先定义MyFilter实现FilterInvocationSecurityMetadataSource
主要就是分析出需要哪些角色
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@AutowiredMyFilter myFilter;@AutowiredMyAccessDecisionManager myAccessDecisionManager;@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}// @Bean
// RoleHierarchy roleHierarchy() {
// RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
// String hierarchy = "dba > admin \n admin > user";
// roleHierarchy.setHierarchy(hierarchy);
// return roleHierarchy;
// }
@Bean
@Override
protected UserDetailsService userDetailsService() {return super.userDetailsService();
}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setAccessDecisionManager(myAccessDecisionManager);o.setSecurityMetadataSource(myFilter);return o;}}).and().formLogin().permitAll().and().csrf().disable();}
}
2.编写MyAccessDecisionManager继承 AccessDecisionManager
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {/**** @param authentication 当前用户具备的角色* @param o* @param collection 当前路径需要的角色,这里是在MyFiter处理后得到的* @throws AccessDeniedException* @throws InsufficientAuthenticationException*/@Overridepublic void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {for (ConfigAttribute attribute : collection) {if ("ROLE_login".equals(attribute.getAttribute())) {// 如果是返回的ROLE_login说明你请求的路径不存在,所有判断你有没有登录 登录的就直接放行if (authentication instanceof AnonymousAuthenticationToken) {throw new AccessDeniedException("非法请求!");} else {return;}}// 获取我具备的角色Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();// 做匹配for (GrantedAuthority authority : authorities) {if (authority.getAuthority().equals(attribute.getAttribute())) {return;}}}// 例如,我具备某些角色,但是此角色没有此路径的权限,那就是非要请求throw new AccessDeniedException("非法请求!");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}
3.1SecurityConfig的写法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@AutowiredMyFilter myFilter;@AutowiredMyAccessDecisionManager myAccessDecisionManager;@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {//FilterSecurityInterceptor 拦截器@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setAccessDecisionManager(myAccessDecisionManager);o.setSecurityMetadataSource(myFilter);return o;}}).and().formLogin().permitAll().and().csrf().disable();}
}
2.7spring security整合oauth2—oauth2
2.7.1配置资源服务器 ResourceServerConfig
/*** 资源服务器*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {//指定资源id stateless配置基于令牌认证resources.resourceId("rid").stateless(true);}//此处角色路径可以从数据库加载// @Override
// public void configure(HttpSecurity http) throws Exception {
// // 路径的角色
// http.authorizeRequests().antMatchers("/admin/**").hasRole("admin")
// .antMatchers("/user/**").hasRole("user")
// .anyRequest().authenticated();
// }// 从数据库加载如下@AutowiredMyFilter myFilter;@AutowiredMyAccessDecisionManager myAccessDecisionManager;/*** @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setAccessDecisionManager(myAccessDecisionManager);o.setSecurityMetadataSource(myFilter);return o;}}).and().formLogin().permitAll().and().csrf().disable();}}
2.7.2授权服务器 AuthorizationServerConfig
/*** 授权服务器*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {// 支持password的认证模式@AutowiredAuthenticationManager authenticationManager;// 配置redis就会有@AutowiredRedisConnectionFactory redisConnectionFactory;// 密码加密方式@AutowiredUserDetailsService userDetailsService;@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {//配置在内存中//配置认证模式//配置授权模式//资源id(名字)//配置的密码123456clients.inMemory().withClient("password").authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(1800).resourceIds("rid").scopes("all").secret("$2a$10$LcM2.fVWzB50vitKLrPPDugS/owlp.qVVT5jA0EyJuFeez6S5hTkm");}// 配置令牌的存储@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) //.authenticationManager(authenticationManager).userDetailsService(userDetailsService);}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.allowFormAuthenticationForClients();}}
2.7.3security配置文件
@Override@Beanprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Bean@Overrideprotected UserDetailsService userDetailsService() {return super.userDetailsService();}// 静态的加载用户
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication()
// .withUser("javaboy").password("$2a$10$LcM2.fVWzB50vitKLrPPDugS/owlp.qVVT5jA0EyJuFeez6S5hTkm").roles("admin")
// .and()
// .withUser("finde")
// .password("$2a$10$kwLIAqAupvY87OM.O25.Yu1QKEXV1imAv7jWbDaQRFUFWSnSiDEwG")
// .roles("user");
// }
// 从数据库加载用户@AutowiredUserService userService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}//放开oauth相关的请求,不就行拦截@Overrideprotected void configure(HttpSecurity http) throws Exception {http.antMatcher("/oauth/**").authorizeRequests().antMatchers("/oauth/**").permitAll().and().csrf().disable();}
2.7.4 MyFilter
@Component
public class MyFilter implements FilterInvocationSecurityMetadataSource {// 做路径匹配的 提供了路径批评规则AntPathMatcher pathMatcher = new AntPathMatcher();@AutowiredMenuService menuService;/*** 分析请求地址 根据请求的地址,分析出需要哪些角色* @param o* @return* @throws IllegalArgumentException*/@Overridepublic Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {// 请求的地址String requestUrl = ((FilterInvocation) o).getRequestUrl();// 所有的菜单 ,这里可以给一个缓存List<Menu> allMenus = menuService.getAllMenus();for (Menu menu : allMenus) {// 如果请求地址批评上了if (pathMatcher.match(menu.getPattern(), requestUrl)) {List<Role> roles = menu.getRoles();String[] rolesStr = new String[roles.size()];//查看需要哪些角色 rolesStr,防止角色集合for (int i = 0; i < roles.size(); i++) {rolesStr[i] = roles.get(i).getName();}return SecurityConfig.createList(rolesStr);}}// 没有匹配上路径,这个就是标识符号,看在数据库中没有这个路径,怎么处理return SecurityConfig.createList("ROLE_login");}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}
2.7.5 MyAccessDecisionManager
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {/**** @param authentication 当前用户具备的角色* @param o* @param collection 当前路径需要的角色,这里是在MyFiter处理后得到的* @throws AccessDeniedException* @throws InsufficientAuthenticationException*/@Overridepublic void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {for (ConfigAttribute attribute : collection) {if ("ROLE_login".equals(attribute.getAttribute())) {// 如果是返回的ROLE_login说明你请求的路径不存在,所有判断你有没有登录 登录的就直接放行if (authentication instanceof AnonymousAuthenticationToken) {throw new AccessDeniedException("非法请求!");} else {return;}}// 获取我具备的角色Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();// 做匹配for (GrantedAuthority authority : authorities) {if (authority.getAuthority().equals(attribute.getAttribute())) {return;}}}// 例如,我具备某些角色,但是此角色没有此路径的权限,那就是非要请求throw new AccessDeniedException("非法请求!");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}
2.7.6 postman测试 -请求token
post http://127.0.0.1:8981/oauth/token
Body x-www-form-urlencoded
[{“key”:“username”,“value”:“admin”},
{“key”:“password”,“value”:“123”},
{“key”:“grant_type”,“value”:“password”},
{“key”:“client_id”,“value”:“password”},
{“key”:“scope”,“value”:“all”},
{“key”:“client_secret”,“value”:“123456”}]
{"username":"admin", "password":"123","grant_type":"password","client_id":"password","scope":"all","client_secret":"123456",
}
结果
{
“access_token”: “d1bdfd01-e06a-4b4e-a661-a49012da9afa”,
“token_type”: “bearer”,
“refresh_token”: “d31082b6-68a7-46b5-937a-62a24b186970”,
“expires_in”: 1325,
“scope”: “all”
}
换回新的token
{"grant_type":"refresh_token", "refresh_token":"d31082b6-68a7-46b5-937a-62a24b186970","grant_type":"password","client_secret":"123456",
}
2.8spring security支持json登录—security-dy/security-db/oauth2
2.8.1重写UsernamePasswordAuthenticationFilter方法
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throwsAuthenticationException {if (!request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {//说明用户以 JSON 的形式传递的参数String username = null;String password = null;try {Map<String, String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);username = map.get("username");password = map.get("password");} catch (IOException e) {e.printStackTrace();}if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}return super.attemptAuthentication(request, response);}
2.8.2注入重写的方法
在securityconfig中写入一下文件
// 配置支持json登录@BeanMyAuthenticationFilter myAuthenticationFilter() throws Exception {MyAuthenticationFilter filter = new MyAuthenticationFilter();filter.setAuthenticationManager(authenticationManagerBean());return filter;}/**** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.addFilterAt(myAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}
2.8.3配置登录成功后的转跳和失败后转跳
在myAuthenticationFilter中配置
此时成功和失败的回调只能在fiter中配置,cnfigure中的配置是失效的
// 配置支持json登录@BeanMyAuthenticationFilter myAuthenticationFilter() throws Exception {MyAuthenticationFilter filter = new MyAuthenticationFilter();filter.setAuthenticationManager(authenticationManagerBean());filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() { //登录成功的处理@Override// authentication保存了登录成功后的信息public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", authentication.getPrincipal());out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}});filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {//登录失败的处理@Override // e登录失败的异常public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,AuthenticationException e) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 401);if (e instanceof LockedException) {map.put("msg", "账户被锁定,登录失败!");} else if (e instanceof BadCredentialsException) {map.put("msg", "用户名或密码输入错误,登录失败!");} else if (e instanceof DisabledException) {map.put("msg", "账户被禁用,登录失败!");} else if (e instanceof AccountExpiredException) {map.put("msg", "账户过期,登录失败!");} else if (e instanceof CredentialsExpiredException) {map.put("msg", "密码过期,登录失败!");} else {map.put("msg", "登录失败!");}out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}});return filter;}
2.9spring security整合jwt –jwt-demo
2.9.1 JwtLoginFilter 登录的时候给用户token
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {// 实现构造方法/**** @param defaultFilterProcessesUrl* @param authenticationManager*/public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {super(new AntPathRequestMatcher(defaultFilterProcessesUrl));setAuthenticationManager(authenticationManager);}/*** 提取用户名和密码去做登录** @param req* @param httpServletResponse* @return* @throws AuthenticationException* @throws IOException*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse httpServletResponse)throws AuthenticationException, IOException {// 为了获取登录的数据转换成userUser user = new ObjectMapper().readValue(req.getInputStream(), User.class);return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));}// 登录成功的回调 生成jwt@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse resp,FilterChain chain, Authentication authResult) throws IOException, ServletException {Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();//获取登录用户的角色StringBuffer sb = new StringBuffer();for (GrantedAuthority authority : authorities) {//获取当前角色sb.append(authority.getAuthority()).append(",");}// 生成jwt的tokenString jwt = Jwts.builder().claim("authorities", sb)// 用户的角色.setSubject(authResult.getName()) // 主题.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000)) // 过期时间.signWith(SignatureAlgorithm.HS512, "findme") // 签名的算法.compact();Map<String, String> map = new HashMap<>();map.put("token", jwt);map.put("msg", "登录成功");resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}// 登录失败/***** @param req* @param resp* @param failed* @throws IOException* @throws ServletException 登录失败的异常,根据异常判断失败的原因*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse resp,AuthenticationException failed) throws IOException, ServletException {Map<String, String> map = new HashMap<>();map.put("msg", "登录失败");resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}
2.9.2. 访问系统的时候效验token
public class JwtFilter extends GenericFilterBean {/*** 用户每次登录的时候校验token** @param servletRequest* @param servletResponse* @param filterChain* @throws IOException* @throws ServletException*/@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) servletRequest;// token 放在很多地方都可以,此处是默认把token放到请求头中String jwtToken = req.getHeader("authorization");// 解析tokenJws<Claims> jws = Jwts.parser().setSigningKey("findme") // 生成token的签名.parseClaimsJws(jwtToken.replace("Bearer", "")); // 前端的token会自动加一个Bearer,此处需要去掉Claims claims = jws.getBody(); // 登录的信息String username = claims.getSubject();// 用户名 刚才在生成的时候放入了// 拿到登录的角色后要转换成一个list集合解析 此处是当前用户的角色List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));// new 一个新的tokenUsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities);SecurityContextHolder.getContext().setAuthentication(token);// 对过滤器放行filterChain.doFilter(servletRequest,servletResponse);}
2.9.3增加security的相关配置 configure_(HttpSecurity http) _
// jwt 相关配置http.authorizeRequests().antMatchers(HttpMethod.POST, "/login").permitAll().anyRequest().authenticated().and()// 登录的过滤器.addFilterBefore(new JwtLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)// 做校验的过滤器.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class).csrf().disable();
2.10 oauth2 整合jwt+swagger —swcurity-swagger/ 松哥例子swagger-jwt
注意在引入依赖的时候引入cloud相关的
2.10.1.AccessTokenConfig
package com.find.securityswagger.auth;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;/*** @ClassName AccessTokenConfig* @Description token* @Author find me* @Date 2020/6/25 0025 20:37* @Version 1.0*/
@Configuration
public class AccessTokenConfig {// TokenStore 我们使用 JwtTokenStore 这个实例。// 使用了 JWT,access_token 实际上就不用存储了(无状态登录,服务端不需要保存信息),// 因为用户的所有信息都在 jwt 里边,所以这里配置的 JwtTokenStore 本质上并不是做存储。@BeanTokenStore tokenStore() {return new JwtTokenStore(jwtAccessTokenConverter());}// 另外我们还提供了一个 JwtAccessTokenConverter,// 这个 JwtAccessTokenConverter 可以实现将用户信息和 JWT 进行转换(将用户信息转为 jwt 字符串,或者从 jwt 字符串提取出用户信息)。// 另外,在 JWT 字符串生成的时候,我们需要一个签名,这个签名需要自己保存好。@BeanJwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("findme");return converter;}
}
2.10.2AuthorizationServer
package com.find.securityswagger.auth;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;import java.util.Arrays;/*** @ClassName JwtAccessTokenConverter* @Description 将用户信息和 JWT 进行转换* @Author find me* @Date 2020/6/25 0025 20:39* @Version 1.0*/
@EnableAuthorizationServer
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {@AutowiredTokenStore tokenStore;@AutowiredClientDetailsService clientDetailsService;@AutowiredAuthenticationManager authenticationManager;@AutowiredPasswordEncoder passwordEncoder;@AutowiredJwtAccessTokenConverter jwtAccessTokenConverter;//主要用来配置 Token 的一些基本信息,// 例如 Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期等等。// Token 有效期这个好理解,刷新 Token 的有效期我说一下,// 当 Token 快要过期的时候,我们需要获取一个新的 Token,在获取新的 Token 时候,// 需要有一个凭证信息,这个凭证信息不是旧的 Token,而是另外一个 refresh_token,这个 refresh_token 也是有有效期的。@BeanAuthorizationServerTokenServices tokenServices() {DefaultTokenServices services = new DefaultTokenServices();services.setClientDetailsService(clientDetailsService);services.setSupportRefreshToken(true);services.setTokenStore(tokenStore);services.setAccessTokenValiditySeconds(60 * 60 * 24 * 2);services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));services.setTokenEnhancer(tokenEnhancerChain);return services;}//用来配置令牌端点的安全约束,也就是这个端点谁能访问,谁不能访问。@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.allowFormAuthenticationForClients();}//用来配置客户端的详细信息@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("findme").secret(passwordEncoder.encode("123")) //.resourceIds("rid")//资源id.authorizedGrantTypes("password", "refresh_token")//授权类型.scopes("all").redirectUris("http://localhost:6004/index.html");//重定向 uri}// 这里用来配置令牌的访问端点和令牌服务。@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager).tokenServices(tokenServices());}
}
2.10.3ResourceServerConfig
package com.find.securityswagger.auth;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;/*** @ClassName ResourceServerConfig* @Description* @Author find me* @Date 2020/6/25 0025 20:41* @Version 1.0*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@AutowiredTokenStore tokenStore;//首先在 configure 方法中配置资源 ID 和 TokenStore,这里配置好之后,// 会自动调用 JwtAccessTokenConverter 将 jwt 解析出来,jwt 里边就// 包含了用户的基本信息,所以就不用远程校验 access_token 了。@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {//指定资源id stateless配置基于令牌认证
// resources.resourceId("rid").stateless(true);resources.resourceId("rid").tokenStore(tokenStore);}@AutowiredMyFilter myFilter;@AutowiredMyAccessDecisionManager myAccessDecisionManager;/*** @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setAccessDecisionManager(myAccessDecisionManager);o.setSecurityMetadataSource(myFilter);return o;}}).and().formLogin().permitAll().and().csrf().disable();}
}
2.10.4 GlobalCorsConfiguration —支持跨域
package com.find.securityswagger.swaggerconfig;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;/*** @ClassName GlobalCorsConfiguration* @Description 支持跨域* @Author find me* @Date 2020/6/25 0025 21:36* @Version 1.0*/
@Configuration
public class GlobalCorsConfiguration {@Beanpublic CorsFilter corsFilter() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setAllowCredentials(true);corsConfiguration.addAllowedOrigin("*");corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);return new CorsFilter(urlBasedCorsConfigurationSource);}
}
package com.find.securityswagger.swaggerconfig;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.OAuthBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.Arrays;
import java.util.List;/*** @ClassName Swagger2Config* @Description swagger* @Author find me* @Date 2020/6/25 0025 21:19* @Version 1.0*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
// // 手动添加token的方式
// @Bean
// Docket docket() {
// return new Docket(DocumentationType.SWAGGER_2)
// .select()
// .apis(RequestHandlerSelectors.basePackage("com.find.securityswagger.controller"))
// .paths(PathSelectors.any())
// .build()
// .securityContexts(Arrays.asList(securityContexts()))
// .securitySchemes(Arrays.asList(securitySchemes()))
// .apiInfo(new ApiInfoBuilder()
// .description("接口文档的描述信息")
// .title("微人事项目接口文档")
// .contact(new Contact("javaboy","https://www.yuque.com/findme","2354827879@qq.com"))
// .version("v1.0")
// .license("Apache2.0")
// .build());
// }
// //通过 securitySchemes 来配置全局参数,这里的配置是一个名为 Authorization 的请求头(OAuth2 中需要携带的请求头)。
// private SecurityScheme securitySchemes() {
// return new ApiKey("Authorization", "Authorization", "header");
// }
//
// // 则用来配置有哪些请求需要携带 Token,这里我们配置了所有请求。
// private SecurityContext securityContexts() {
// return SecurityContext.builder()
// .securityReferences(defaultAuth())
// .forPaths(PathSelectors.any())
// .build();
// }
//
// // 参数
// private List<SecurityReference> defaultAuth() {
// AuthorizationScope authorizationScope = new AuthorizationScope("xxx", "描述信息");
// AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
// authorizationScopes[0] = authorizationScope;
// return Arrays.asList(new SecurityReference("Authorization", authorizationScopes));
// }// 可以在页面去配置整个登录信息@BeanDocket docket() {return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.basePackage("com.find.securityswagger.controller")).paths(PathSelectors.any()).build().securityContexts(Arrays.asList(securityContext())).securitySchemes(Arrays.asList(securityScheme())).apiInfo(new ApiInfoBuilder().description("接口文档的描述信息").title("find me demo ").contact(new Contact("findme","https://www.yuque.com/findme","2354827879@qq.com")).version("v1.0").license("Apache2.0").build());}private AuthorizationScope[] scopes() {return new AuthorizationScope[]{new AuthorizationScope("all", "all scope")};}// 构建时即得配置 token 的获取地址。private SecurityScheme securityScheme() {GrantType grant = new ResourceOwnerPasswordCredentialsGrant("http://127.0.0.1:6004/oauth/token");return new OAuthBuilder().name("OAuth2").grantTypes(Arrays.asList(grant)).scopes(Arrays.asList(scopes())).build();}private SecurityContext securityContext() {return SecurityContext.builder().securityReferences(Arrays.asList(new SecurityReference("OAuth2", scopes()))).forPaths(PathSelectors.any()).build();}
}
swagger的两种配置的效果如下
输入 Bearer ${token}
postman测试参数如下 先获取token,在传递token
2.11configure其他配置
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//开启配置.antMatchers("/admin/**").hasRole("admin")//路径符合这个需要admin角色
// .antMatchers("user/**").hasAnyRole("admin", "user")//路径符合这个,需要这两个的任意一个.antMatchers("/user/**").access("hasAnyRole('user','admin')").anyRequest().authenticated()//其他请求,登录后就可以访问.and().formLogin()//表单登录.loginProcessingUrl("/doLogin")//处理登录请求的地址.loginPage("/login")//配置登录页面(实际上还是一个请求地址) 前后端分类不存在这种页面 这里还是访问的应该请求,会根据这请求去返回登录页面.usernameParameter("uname")//修改登录的key.passwordParameter("passwd")//修改登录的key.successHandler(new AuthenticationSuccessHandler() { //登录成功的处理@Override// authentication保存了登录成功后的信息public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", authentication.getPrincipal());out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}}).failureHandler(new AuthenticationFailureHandler() {//登录失败的处理@Override // e登录失败的异常public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,AuthenticationException e) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 401);if (e instanceof LockedException) {map.put("msg", "账户被锁定,登录失败!");} else if (e instanceof BadCredentialsException) {map.put("msg", "用户名或密码输入错误,登录失败!");} else if (e instanceof DisabledException) {map.put("msg", "账户被禁用,登录失败!");} else if (e instanceof AccountExpiredException) {map.put("msg", "账户过期,登录失败!");} else if (e instanceof CredentialsExpiredException) {map.put("msg", "密码过期,登录失败!");} else {map.put("msg", "登录失败!");}out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}}).permitAll().and().logout().logoutUrl("/logout") //配置注销请求的地址.logoutSuccessHandler(new LogoutSuccessHandler() {//注销成功后的回调@Overridepublic void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp,Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", "注销登录成功!");out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();}}).and().csrf().disable();/*** 权限不足处理*/http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);Map<String, Object> failureMap = new HashMap<>();failureMap.put("code", 403);failureMap.put("msg", "权限不足!");httpServletResponse.getWriter().println(objectMapper.writeValueAsString(failureMap));}});
// /**
// * 未登陆处理
// */
// http.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
// @Override
// public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
//
//
// httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//
// Map<String, Object> failureMap = new HashMap<>();
// failureMap.put("code", 401);
// failureMap.put("msg", "请先登录!");
//
// httpServletResponse.getWriter().println(objectMapper.writeValueAsString(failureMap));
//
// }
// });
//