- 1.创建项目,引入依赖
- 2.security的其他配置方式
- 2.2基于内存
- 2.3HttpSecurity(入门配置此文件)
- 2.4多个HttpSecurity的配置(复杂业务场景下)—-security
- 2.5配置方法安全
- 2.6基于数据库的认证(基础入门)——-sercuty-db/sercurity-dy
- 2.7spring security整合oauth2—-oauth2
- 2.8spring security支持json登录—-security-dy/security-db/oauth2
- 2.9spring security整合jwt —jwt-demo
- 2.10 oauth2 整合jwt+swagger —-swcurity-swagger/ 松哥例子swagger-jwt
- -1.configure其他配置
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
更多的自定义配置,例如基于内存的认证,配置方式如下:
@Configurationpublic 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("江南一点雨").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(入门配置此文件)
根据实际情况进行角色配置
@Configurationpublic 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("江南一点雨").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注解
@Servicepublic 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
/*** @Author 江南一点雨* UserDetails相当于一个规范* @Site www.javaboy.org 2019-08-11 17:08*/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
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-11 17:16*/@Configurationpublic 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
主要就是分析出需要哪些角色
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-11 17:16*/@Configurationpublic 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@Overrideprotected 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
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-11 17:38*/@Componentpublic 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的写法
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-11 17:16*/@Configurationpublic 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
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-12 23:15* 资源服务器*/@Configuration@EnableResourceServerpublic 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
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-12 23:01* 授权服务器*/@Configuration@EnableAuthorizationServerpublic 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("江南一点雨")// .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
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-11 17:24*/@Componentpublic 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
/*** @Author 江南一点雨* @Site www.javaboy.org 2019-08-11 17:38*/@Componentpublic 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
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*/@Configurationpublic 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@Configurationpublic 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@EnableResourceServerpublic 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*/@Configurationpublic 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@EnableSwagger2public 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
-1.configure其他配置
// 配置不做拦截的地址@Overridepublic void configure(WebSecurity web) throws Exception {// 返回login页就直接过,不用拦截web.ignoring().antMatchers("/login");web.ignoring().antMatchers("/swagger-ui.html").antMatchers("/webjars/**").antMatchers("/v2/**").antMatchers("/swagger-resources/**");// web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");}@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));//// }// });//

