SpringBoot Security前后端分离登录验证的实现
最近一段时间在研究OAuth2的使用,想整个单点登录,从网上找了很多demo都没有实施成功,也许是因为压根就不懂OAuth2的原理导致。有那么几天,越来越没有头绪,又不能放弃,转过头来一想,OAuth2是在Security的基础上扩展的,对于Security自己都是一无所知,干脆就先研究一下Security吧,先把Security搭建起来,找找感觉。
说干就干,在现成的SpringBoot2.1.4.RELEASE环境下,进行Security的使用。
简单的Security的使用就不说了,目前的项目是前后端分离的,登录成功或者失败返回的数据格式必须JSON形式的,未登录时也需要返回JSON格式的提示信息,退出时一样需要返回JSON格式的数据。授权先不管,先返回JSON格式的数据,这一个搞定,也研究了好几天,翻看了很多别人的经验,别人的经验有的看得懂,有的看不懂,关键时刻还需要自己研究呀。
下面,上代码:
第一步,在pom.xml中引入Security配置文件
org.springframework.boot spring-boot-starter-security
第二步,增加Configuration配置文件
importjava.io.PrintWriter; importjava.util.HashMap; importjava.util.Map; importjavax.servlet.http.HttpServletResponse; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importorg.springframework.http.HttpMethod; importorg.springframework.security.authentication.AuthenticationProvider; importorg.springframework.security.authentication.BadCredentialsException; importorg.springframework.security.authentication.DisabledException; importorg.springframework.security.authentication.dao.DaoAuthenticationProvider; importorg.springframework.security.config.annotation.web.builders.HttpSecurity; importorg.springframework.security.config.annotation.web.builders.WebSecurity; importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; importorg.springframework.security.core.userdetails.UserDetailsService; importorg.springframework.security.core.userdetails.UsernameNotFoundException; importcom.fasterxml.jackson.databind.ObjectMapper; /** *参考网址: *https://blog.csdn.net/XlxfyzsFdblj/article/details/82083443 *https://blog.csdn.net/lizc_lizc/article/details/84059004 *https://blog.csdn.net/XlxfyzsFdblj/article/details/82084183 *https://blog.csdn.net/weixin_36451151/article/details/83868891 *查找了很多文件,有用的还有有的,感谢他们的辛勤付出 *Security配置文件,项目启动时就加载了 *@author程就人生 * */ @Configuration publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{ @Autowired privateMyPasswordEncodermyPasswordEncoder; @Autowired privateUserDetailsServicemyCustomUserService; @Autowired privateObjectMapperobjectMapper; @Override protectedvoidconfigure(HttpSecurityhttp)throwsException{ http .authenticationProvider(authenticationProvider()) .httpBasic() //未登录时,进行json格式的提示,很喜欢这种写法,不用单独写一个又一个的类 .authenticationEntryPoint((request,response,authException)->{ response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriterout=response.getWriter(); Mapmap=newHashMap (); map.put("code",403); map.put("message","未登录"); out.write(objectMapper.writeValueAsString(map)); out.flush(); out.close(); }) .and() .authorizeRequests() .anyRequest().authenticated()//必须授权才能范围 .and() .formLogin()//使用自带的登录 .permitAll() //登录失败,返回json .failureHandler((request,response,ex)->{ response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriterout=response.getWriter(); Map map=newHashMap (); map.put("code",401); if(exinstanceofUsernameNotFoundException||exinstanceofBadCredentialsException){ map.put("message","用户名或密码错误"); }elseif(exinstanceofDisabledException){ map.put("message","账户被禁用"); }else{ map.put("message","登录失败!"); } out.write(objectMapper.writeValueAsString(map)); out.flush(); out.close(); }) //登录成功,返回json .successHandler((request,response,authentication)->{ Map map=newHashMap (); map.put("code",200); map.put("message","登录成功"); map.put("data",authentication); response.setContentType("application/json;charset=utf-8"); PrintWriterout=response.getWriter(); out.write(objectMapper.writeValueAsString(map)); out.flush(); out.close(); }) .and() .exceptionHandling() //没有权限,返回json .accessDeniedHandler((request,response,ex)->{ response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriterout=response.getWriter(); Map map=newHashMap (); map.put("code",403); map.put("message","权限不足"); out.write(objectMapper.writeValueAsString(map)); out.flush(); out.close(); }) .and() .logout() //退出成功,返回json .logoutSuccessHandler((request,response,authentication)->{ Map map=newHashMap (); map.put("code",200); map.put("message","退出成功"); map.put("data",authentication); response.setContentType("application/json;charset=utf-8"); PrintWriterout=response.getWriter(); out.write(objectMapper.writeValueAsString(map)); out.flush(); out.close(); }) .permitAll(); //开启跨域访问 http.cors().disable(); //开启模拟请求,比如APIPOST测试工具的测试,不开启时,APIPOST为报403错误 http.csrf().disable(); } @Override publicvoidconfigure(WebSecurityweb){ //对于在header里面增加token等类似情况,放行所有OPTIONS请求。 web.ignoring().antMatchers(HttpMethod.OPTIONS,"/**"); } @Bean publicAuthenticationProviderauthenticationProvider(){ DaoAuthenticationProviderauthenticationProvider=newDaoAuthenticationProvider(); //对默认的UserDetailsService进行覆盖 authenticationProvider.setUserDetailsService(myCustomUserService); authenticationProvider.setPasswordEncoder(myPasswordEncoder); returnauthenticationProvider; } }
第三步,实现UserDetailsService接口
importorg.springframework.security.core.userdetails.UserDetails; importorg.springframework.security.core.userdetails.UserDetailsService; importorg.springframework.security.core.userdetails.UsernameNotFoundException; importorg.springframework.stereotype.Component; /** *登录专用类 *自定义类,实现了UserDetailsService接口,用户登录时调用的第一类 *@author程就人生 * */ @Component publicclassMyCustomUserServiceimplementsUserDetailsService{ /** *登陆验证时,通过username获取用户的所有权限信息 *并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用 */ @Override publicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{ //在这里可以自己调用数据库,对username进行查询,看看在数据库中是否存在 MyUserDetailsmyUserDetail=newMyUserDetails(); myUserDetail.setUsername(username); myUserDetail.setPassword("123456"); returnmyUserDetail; } }
说明:这个类,主要是用来接收登录传递过来的用户名,然后可以在这里扩展,查询该用户名在数据库中是否存在,不存在时,可以抛出异常。本测试为了演示,把数据写死了。
第四步,实现PasswordEncoder接口
importorg.springframework.security.crypto.password.PasswordEncoder; importorg.springframework.stereotype.Component; /** *自定义的密码加密方法,实现了PasswordEncoder接口 *@author程就人生 * */ @Component publicclassMyPasswordEncoderimplementsPasswordEncoder{ @Override publicStringencode(CharSequencecharSequence){ //加密方法可以根据自己的需要修改 returncharSequence.toString(); } @Override publicbooleanmatches(CharSequencecharSequence,Strings){ returnencode(charSequence).equals(s); } }
说明:这个类主要是对密码加密的处理,以及用户传递过来的密码和数据库密码(UserDetailsService中的密码)进行比对。
第五步,实现UserDetails接口
importjava.util.Collection; importorg.springframework.security.core.GrantedAuthority; importorg.springframework.security.core.userdetails.UserDetails; importorg.springframework.stereotype.Component; /** *实现了UserDetails接口,只留必需的属性,也可添加自己需要的属性 *@author程就人生 * */ @Component publicclassMyUserDetailsimplementsUserDetails{ /** * */ privatestaticfinallongserialVersionUID=1L; //登录用户名 privateStringusername; //登录密码 privateStringpassword; privateCollectionauthorities; publicvoidsetUsername(Stringusername){ this.username=username; } publicvoidsetPassword(Stringpassword){ this.password=password; } publicvoidsetAuthorities(Collectionauthorities){ this.authorities=authorities; } @Override publicCollectiongetAuthorities(){ returnthis.authorities; } @Override publicStringgetPassword(){ returnthis.password; } @Override publicStringgetUsername(){ returnthis.username; } @Override publicbooleanisAccountNonExpired(){ returntrue; } @Override publicbooleanisAccountNonLocked(){ returntrue; } @Override publicbooleanisCredentialsNonExpired(){ returntrue; } @Override publicbooleanisEnabled(){ returntrue; } }
说明:这个类是用来存储登录成功后的用户数据,登录成功后,可以使用下列代码获取:
MyUserDetailsmyUserDetails=(MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
代码写完了,接下来需要测试一下,经过测试才能证明代码的有效性,先用浏览器吧。
第一步测试,未登录前访问index,页面直接重定向到默认的login页面了,测试接口OK。
第二步测试,登录login后,返回了json数据,测试结果OK。
第三步测试,访问index,返回输出的登录数据,测试结果OK。
第四步,访问logout,返回json数据,测试接口OK。
第五步,用APIPOST测试,用这个工具模拟ajax请求,看请求结果如何,首先访问index,这个必须登录后才能访问。测试结果ok,返回了我们需要的JSON格式数据。
第六步,在登录模拟对话框,设置环境变量,以保持登录状态。
**第七步,登录测试,返回JSON格式的数据,测试结果OK。
第八步,在返回到index测试窗口,发送请求,返回当前用户JSON格式的信息,测试结果OK。
第九步,测试退出,返回JSON格式数据,测试结果OK
第十步,退出后,再访问index,出现问题,登录信息还在,LOOK!
把头部的header前面的勾去掉,也就是去掉cookie,这时正常了,原因很简单,在退出时,没有清除cookie,这个只能到正式的环境上去测了。APIPOST再怎么模拟还是和正式环境有区别的。
如果在APIPOST测试报403错误,那就需要把configuration配置文件里的
//开启跨域访问 http.cors().disable(); //开启模拟请求,比如APIPOST测试工具的测试,不开启时,APIPOST为报403错误 http.csrf().disable();
到此这篇关于SpringBootSecurity前后端分离登录验证的实现的文章就介绍到这了,更多相关SpringBootSecurity登录验证内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。