Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

spring security入门demo

不懂是非 2019-02-16 16:23:00 阅读数:248 评论数:0 点赞数:0 收藏数:0

一、前言

  因项目需要引入spring security权限框架,而之前也没接触过这个一门,于是就花了点时间弄了个小demo出来,说实话,刚开始接触这个确实有点懵,看网上资料写的权限大都是静态,即就是在配置文件或代码里面写定角色,不能动态更改,个人感觉这样实际场景应该应用的不多,于是就进一步研究,整理出了一个可以动态管理个人权限角色demo,其中可能有很多不足或之处,还望指正。本文通过spring boot集成spring security,处理方式没有使用xml文件格式,而是用了注解。

 二、表结构

接触过权限这块的,大都应该知道,最核心的有三张表(当然,如果牵涉业务复杂,可能不止)。

一、用户表

二、角色表

三、菜单表(即权限表)

剩余还有两张多对多的表。即用户与角色,角色与菜单。如下图

三、spring security入口

由于本文只是着重说spring security,关于spring boot一块内容会直接带过。如spring boot启动类配置等。

首先会自定义一个类去实现WebSecurityConfigurerAdapter类。重写其中几个方法,代码如下1 @Configuration2 public class WebSecurityConfig extendsWebSecurityConfigurerAdapter {3 4 @Autowired5 @Qualifier(value = "userDetailServiceImpl")6 privateUserDetailsService userDetailsService;7 8 @Autowired9 privateLoginSuccessAuthenticationHandler successAuthenticationHandler;10 11 @Autowired12 privateLoginFailureAuthenticationHandler failureAuthenticationHandler;13 14 @Autowired15 privateAuthenticationAccessDeniedHandler accessDeniedHandler;16 17 @Autowired18 privateUrlAccessDecisionManager decisionManager;19 20 @Autowired21 privateUrlPathFilterInvocationSecurityMetadataSource urlPathFilterInvocationSecurityMetadataSource;22 23 @Autowired24 privateAuthenticationProvider authenticationProvider;25 26 @Autowired27 privatePasswordEncoder passwordEncoder;28 29 @Override30 protected void configure(AuthenticationManagerBuilder auth) throwsException {31 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);32 auth.authenticationProvider(authenticationProvider);33 }34 35 @Override36 public voidconfigure(WebSecurity web) {37 web.ignoring().antMatchers("/index.html","/favicon.ico");38 }39 40 @Override41 protected void configure(HttpSecurity http) throwsException {42 http.csrf().disable()43 .authorizeRequests()44 .withObjectPostProcessor(new ObjectPostProcessor() {45 @Override46 public O postProcess(O o) {47 o.setAccessDecisionManager(decisionManager);48 o.setSecurityMetadataSource(urlPathFilterInvocationSecurityMetadataSource);49 returno;50 }51 })52 53 .anyRequest()54 .authenticated()//其他 url 需要身份认证 55 56 .and()57 .formLogin() //开启登录,如果不指定登录路径(即输入用户名和密码表单提交的路径),则会默认为spring securtiy的内部定义的路径 58 .successHandler(successAuthenticationHandler)59 .failureHandler(failureAuthenticationHandler)// 遇到用户名或密码不正确/用户被锁定等情况异常,会交给此handler处理60 .permitAll()61 62 .and()63 .logout()64 .logoutUrl("/logout")//退出操作,其实也有一个handler,如果没其他业务逻辑,可以默认为spring security的handler65 .permitAll()66 .and()67 .exceptionHandling().accessDeniedHandler(accessDeniedHandler);68 }

在这里会介绍以下几个类作用

一、UserDetailsService二、AuthenticationProvider三、AuthenticationAccessDeniedHandler四、UrlAccessDecisionManager五、UrlPathFilterInvocationSecurityMetadataSource至于LoginSuccessAuthenticationHandler、LoginFailureAuthenticationHandler就是用来处理登录成功和登录失败情况,这里不做介绍

3.1、UserDetailService的作用

这个一个接口,通常我们需要去实现它,作用主要是用来我们和数据库做交互用的。简单来说,就是用户名传过来,这个类负责校验用户名是否存在等业务逻辑。1 @Component2 public class UserDetailServiceImpl implementsUserDetailsService {3 4 @Autowired5 privateSysUserDAO userDAO;6 7 @Autowired8 privatePasswordEncoder passwordEncoder;9 10 11 @Override12 public UserDetails loadUserByUsername(String s) throwsUsernameNotFoundException {13 SysUser sysUser =userDAO.findByUsername(s);14 if (sysUser == null){15 throw new UsernameNotFoundException("用户不存在");16 }17 String pwd =passwordEncoder.encode(sysUser.getPassword());18 System.out.println(pwd);19 return newUser(sysUser.getUsername(),pwd,getRoles(sysUser.getRoles()));20 }21 22 private Collection getRoles(Listroles){23 List list = new ArrayList<>();24 for(SysRole role : roles){25 SimpleGrantedAuthority grantedAuthority = newSimpleGrantedAuthority(role.getRoleName());26 list.add(grantedAuthority);27 }28 returnlist;29 }30 }

 

代码比较简单,值得注意的是sercurity里的User对象,它的一个构造函数有是哪个参数值,第一个和第二个是用户名和密码,密码作用就是后面用来校验前端传过来的密码正确性。稍后会讲到。至于第三个参数就是当前用户所拥有的角色,作用就是在当前端请求一个接口的时候,会判断这个接口所拥有的权限和该用户所有的权限有重合,简单来说就是该用户是否拥有该接口权限。这里也就实现了一个角色可以动态修改的功能。因其实从数据库查询出来。

3.2、AuthenticationProvider

它也是一个接口,它的作用是用来校验用户密码等功能,当然如短信验证或要第三方验证,也可以实现这个接口,在本文中是用密码校验。前面也说到userDetailService会传一个用户的基本信息。它的主要作用就是为该接口服务的。1 @Component2 public class LoginAuthenticationProvider implementsAuthenticationProvider {3 4 @Autowired5 privateUserDetailsService userDetailsService;6 7 @Override8 public Authentication authenticate(Authentication authentication) throwsAuthenticationException {9 //获取表单用户名 10 String username =(String) authentication.getPrincipal();11 //获取表单用户填写的密码 12 String password =(String) authentication.getCredentials();13 14 UserDetails userDetails =userDetailsService.loadUserByUsername(username);15 16 String password1 =userDetails.getPassword();17 if (!Objects.equals(password,password1)){18 throw new BadCredentialsException("用户名或密码不正确");19 }20 21 return newUsernamePasswordAuthenticationToken(username,password,userDetails.getAuthorities());22 }23 24 @Override25 public boolean supports(ClassaClass) {26 return true;27 }28 }

 

值得注意的是如果验证通过会返回一个UsernamePasswordAuthenticationToken对象,它的作用就是标志着此用户已通过登录验证,如果没通过,则spring security会捕捉如代码18行的异常,然后再包装一个匿名的token,即AnonymousAuthenticationToken,此token即代表用户未登录。两个接口主要服务于用户登录这块。接下来的三个是服务于权限校验。即接口验证

3.3、UrlPathFilterInvocationSecurityMetadataSource

 它的作用是用来处理当前用户是否拥有此接口的权限。1 @Component2 public class UrlPathFilterInvocationSecurityMetadataSource implementsFilterInvocationSecurityMetadataSource {3 4 5 @Autowired6 privateSysMenuDAO sysMenuDAO;7 8 private AntPathMatcher antPathMatcher = newAntPathMatcher();9 10 @Override11 public Collection getAttributes(Object object) throwsIllegalArgumentException {12 FilterInvocation filterInvocation =(FilterInvocation) object;13 String requestUrl =filterInvocation.getRequestUrl();14 //因为菜单一般随着开发完成,变动不大,此处可以使用缓存,这里为了演示,就直接查库,菜单对应角色需要动态情缓存,如变更菜单和角色关系,需清除缓存 15 List all =sysMenuDAO.findAll();16 for(SysMenu menu : all) {17 if (menu.getRoles().size() != 0 &&antPathMatcher.match(menu.getUrlPath(), requestUrl)) {18 List roles =menu.getRoles();19 int size =roles.size();20 String[] values = newString[size];21 for (int i = 0; i < size; i++) {22 values[i] =roles.get(i).getRoleName();23 }24 returnSecurityConfig.createList(values);25 }26 }27 return SecurityConfig.createList("ROLE_LOGIN");28 }29 30 @Override31 public CollectiongetAllConfigAttributes() {32 return null;33 }34 35 @Override36 public boolean supports(Classclazz) {37 return true;38 }39 }

 

从代码就可以看出16行的for循环就是获取当前请求接口锁需要的权限,这里使用spring security的路径匹配类。如果该接口·没有权限,这里返回一个标志如ROLE_LOGIN,当然如果需要其他标志可以自行定义,这里为了简便,就用了这个。

3.4、UrlAccessDecisionManager

这个类就是最终的决策类。从3.1到3.2,大家都清楚,已有的信息,用户所有的权限这个已经获取到了,3.3可知当前请求接口的权限也已经获取到了,剩下的肯定就是比较两这个权限集合有没有交集,如果有则表明当前用户拥有此接口的权限。1 @Component2 public class UrlAccessDecisionManager implementsAccessDecisionManager {3 4 /// 5 /6 /@paramauthentication 当前用户信息,和当前用户的拥有权限信息,即来自于userDetailService里的7 /@paramobject 即FilterInvocation对象,可以获取httpServletRequest请求对象8 /@paramconfigAttributes 本次访问所需要的权限9 /@throwsAccessDeniedException10 /@throwsInsufficientAuthenticationException11 /*/ 12 @Override13 public void decide(Authentication authentication, Object object, Collection configAttributes) throwsAccessDeniedException, InsufficientAuthenticationException {14 Iterator iterator =configAttributes.iterator();15 while(iterator.hasNext()) {16 ConfigAttribute ca =iterator.next();17 //当前请求需要的权限 18 String needRole =ca.getAttribute();19 if ("ROLE_LOGIN".equals(needRole)) {20 //即匿名用户/未登录,如果用户登录成功。那么authententication就是前面提到的UsernamePasswordAuthententicationToken类 21 if (authentication instanceofAnonymousAuthenticationToken) {22 throw new BadCredentialsException("未登录");23 } else {//登录但不具有此路径权限,即前面3.3提到的ROLE_LOGIN,接口没有角色对应,主要用户已经登录成功 24 break;25 }26 }27 //当前用户所具有的权限 28 Collection authorities =authentication.getAuthorities();29 for(GrantedAuthority authority : authorities) {30 if(authority.getAuthority().equals(needRole)) {31 return;32 }33 }34 }35 throw new AccessDeniedException("权限不足!");36 }37 38 @Override39 public booleansupports(ConfigAttribute attribute) {40 return true;41 }42 43 @Override44 public boolean supports(Classclazz) {45 return true;46 }47 }

3.5、AuthenticationAccessDeniedHandler

这个类就是用来接收上面抛出的accessDeniedException异常,1 @Component2 public class AuthenticationAccessDeniedHandler implementsAccessDeniedHandler {3 4 5 @Override6 public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throwsIOException, ServletException {7 httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);8 httpServletResponse.setContentType("application/json;charset=UTF-8");9 PrintWriter writer =httpServletResponse.getWriter();10 11 writer.print("权限不足");12 writer.flush();13 }14 }

 

至于哪种异常由哪个类处理,如果了解源码的都知道spring security有一个异常处理过滤器,名字为ExceptionTranslationFilter,要想进一步了解的,可自行看源码,这里提供一个个人认为写的挺好的博文,链接地址,这里不多说废话。

相信大家看完以上文章,对spring security应该有一个大致的了解,,这里附上一个spring security请求经过的过滤器Filter,

执行顺序从上到下。要想研究一波,大家可以先从DelegatingFilterProxy类及它的父类开始入手,一步一步debug下去,相信会有收获的。关于WebSecurityConfig 的配置情况,这里也不多说,网上文章也挺多的。在这里说下当初遇到的一个比较坑的坑

四、遇到的坑

当时场景是这样的,因为项目采用的是前后端分离模式开发的,后端写完代码需要部署到测试服务器,供前端使用,采用的域名是https模式,使用了nginx代码模式,部署上去后。因为登录失败后,spring security会请求到你指定的一个路径,但此时问题出现了,代码部署上去了,测试了一个用户名和密码不正确的情况,结果发现跳转后的host由https变成了http,例子:本来是请求https://abc.com/doLogin路径,但是变成了htttp://abc.com/doLogin。这肯定是访问不了,当时就有点懵了,后面经过分析发现,更改Nginx配置可以达到指定效果,在指定的location加入proxysetheader X-Forwarded-Proto https,但是这样局限性也有,这样做只能使用https进行访问,所以就没采用,后来就直接百度,百度了的结果大都是更改spring mvc 内部视图解析器配置,如下面

 1 2 3 4 5 6 7

 

 

 

不过redirect也提醒了我,这个情况由https 变成http 应该就是redirect搞的鬼。那如果将spring security内部由redirect改成forward呢,那情况又会怎样,紧接着,又去看其源码,最后发现这样一个类LoginUrlAuthenticationEntryPoint负责spring security的重定向和转发情况,在其commence方法内进行操作,最后那肯定得试试,最后将该类的useForward属性设置成了true,然后就完美解决。

 

 

 --------------------------------------------------------------------------------------------------------------------------------------------------分界线--------------------------------------------------------------------------------------

以上就是全部内容,若有不足之处,还望指正,另外附上本文代码地址供大家参考 spring security demo

版权声明
本文为[不懂是非]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/qm-article/p/10388166.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领