
先说一下SpringSecurity是干什么的SpringSecurity主要作用有2方面认证、授权。认证Authentication 用户认证就是判断一个用户的身份是否合法的过程用户去访问系统资源时系统要求验证用户的身份信息身份合法方可继续访问不合法则拒绝访问。常见的用户身份认证方式有用户名密码登录二维码登录手机短信登录指纹认证等方式。授权Authorize授权是用户认证通过根据用户的权限来控制用户访问资源的过程拥有资源的访问权限则正常访问没有权限则拒绝访问权限管理涉及到几个概念主体用户id、账号、密码、…资源资源id、资源名称、访问地址、…权限权限id、权限标识、权限名称、资源id、…角色角色id、角色名称、…业界通常基于RBAC实现授权。在单体应用中我觉得理解为基于角色的访问控制Role-Based Access Control是比较合适的用起来比较方便。而在当前动辄微服务开发的环境下个人觉得理解为基于资源的访问控制Resource-Based Access Control用起来更方便因为微服务中各个微服务都当做资源来看待了。整合SpringBoot整合SpringSecurity还是比较简单的引入相关jar包配置Security配置时会稍麻烦因为需要理解的比较多1. 引入Jar包比较简单引入web包和security包就行dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency2. 启动测试引入jar包后就可以启动了启动时会生成随机密码随机密码访问项目就会跳转到登陆页面默认账号user登陆SpringSecurity自带注销地址/logout访问这个地址会弹出注销页面。注销3. 自定义配置以上是SpringSecurity自带的认证功能我们使用时需要根据我们自己的需要自定义一些内容2方面配置认证配置授权配置例如登陆的账号密码是否允许表单登陆密码加密的情况权限鉴定…3.1 认证配置自定义的配置其实都在同一个类中认证和授权在不同的方法中配置类继承WebSecurityConfigurerAdapter类重写2个方法就行。注意就是这个父类想要配置什么点进源码去里面找对应的方法认证的方式有2种一是账号密码等认证信息写在配置中二是账号密码等信息从数据库读取。使用时取其一3.1.1 认证信息写在配置中ConfigurationEnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{/** * 1. 认证配置 */Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{// 测试用的写死的账号密码auth.inMemoryAuthentication().withUser(admin).password(passwordEncoder().encode(123456)).roles(ADMIN).authorities(/test/t1).and().withUser(user).password(passwordEncoder().encode(123456)).roles(USER).authorities(/test/t2);}// 设置密码加密方法BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}/** * 2. 授权的配置方法下面再讲先空着 */Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{}}3.1.2 认证信息从数据库拿这种方式配置类简单但是需要一个用户服务类来返回一个SpringSecurity封装的一个user对象直接将服务类放到配置文件中就行。importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;ConfigurationEnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{// 注入服务类AutowiredprivateUserDetailsServiceImpluserDetailsServiceImpl;/** * 1. 认证配置 */Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsServiceImpl);}// 设置密码加密方法必须设置BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}/** * 2. 授权的配置方法下面再讲先空着 */Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{}}看一下这个服务类怎么写的先准备一个服务类要返回的UserDetails对象自定义user实体对象packagecom.example.demo.security.userdetails;importjava.util.Collection;importjava.util.Date;importorg.springframework.format.annotation.DateTimeFormat;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.AuthorityUtils;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importcom.fasterxml.jackson.annotation.JsonFormat;importlombok.Data;/** * 参考{link org.springframework.security.core.userdetails.User}这个类 * 这个类是security设置实体类参数值的时候用的里面很多方法可以参考使用。 * 比如设置roles和设置authorities的过程在User类的内部类UserBuilder中 */DatapublicclassMyUserDetail implements UserDetails{privatestaticfinallong serialVersionUID1L;privateLong userId;privateString username;privateString name;privateString password;privateboolean status;privateLong deptId;privateString email;privateString mobile;privateString sex;privateString avatar;DateTimeFormat(patternyyyy-MM-dd HH:mm:ss)JsonFormat(patternyyyy-MM-dd HH:mm:ss,timezoneGMT8)privateDate lastLoginTime;// 角色权限SpringSecurity中角色和权限都是放在这个里面的使用起来是一样的区别在于角色要加前缀 ROLE_privateCollectionGrantedAuthorityauthorities;/** * 参考{link org.springframework.security.core.userdetails.User.UserBuilder} * 中的roles方法 * param roles * return */publicListGrantedAuthorityroles(String...roles){ListGrantedAuthorityauthoritiesnew ArrayList(roles.length);for(String role:roles){Assert.isTrue(!role.startsWith(ROLE_),()-role cannot start with ROLE_ (it is automatically added));authorities.add(newSimpleGrantedAuthority(ROLE_role));}returnauthorities;}/** * 参考{link org.springframework.security.core.userdetails.User.UserBuilder} * 中的 authorities 方法 * param authorities * return */publicListGrantedAuthorityauthorities(String authorities){returnAuthorityUtils.commaSeparatedStringToAuthorityList(authorities);}publicListGrantedAuthorityauthorities(String...authorities){returnAuthorityUtils.createAuthorityList(authorities);}OverridepublicCollection?extends GrantedAuthoritygetAuthorities(){returnthis.authorities;}OverridepublicbooleanisAccountNonExpired(){// 先按这个判断需要什么自己添加returnthis.isStatus();}OverridepublicbooleanisAccountNonLocked(){returnthis.isStatus();}OverridepublicbooleanisCredentialsNonExpired(){returnthis.isStatus();}OverridepublicbooleanisEnabled(){returnthis.isStatus();}}用户服务类package com.example.demo.security.userdetails;importjava.util.ArrayList;importjava.util.List;importjavax.annotation.Resource;importorg.springframework.security.authentication.DisabledException;importorg.springframework.security.authentication.LockedException;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importcom.example.demo.dao.MyUserDao;publicclassUserDetailsServiceImplimplementsUserDetailsService{ResourceprivateMyUserDaomyUserDao;OverridepublicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{MyUserDetailuserDetailmyUserDao.getMyUserDetail(username);// 注意数据库中保存的密码要是加密过的就是在配置类中设置的加密方法if(!userDetail.isEnabled()){thrownewDisabledException(账号状态异常);}elseif(!userDetail.isCredentialsNonExpired()){thrownewLockedException(密码过期);}// 模拟一点角色权限信息角色前面要加 ROLE_ 前缀userDetail.setAuthorities(userDetail.authorities(/test/t1,/test/t2,ROLE_ADMIN,ROLE_ROOT));returnuserDetail;}}使用上面这2种方法之一我们就可以用我们自己的账号和密码登陆了。3.1.3 看看SpringSecurity自带的上面是我们自己实现的接口写了过程。其实SpringSecurity自己也封装了很多我们也可以看看。实现类官方包里面的就是用户的实现过程其实我们可以用自带的这些但是限制比较多拿jdbc这个来说他也重写了loadUsersByUsername()。JdbcUserDetailsManager但是它限制了很多东西表名、字段等要符合人家要求你要有users表表中要包含这些字段sql如果你要想使用初始化时传入DataSource即可他会根据你传入的数据源自动查找数据。构造方法3.2 授权配置**注意**前提条件是认证时账号信息中加入了角色和权限的一些信息这里才能进行权限判定。授权配置常用的有2种方式一是在SecurityConfig类中一是用注解表达式。3.2.1 在SecurityConfig类配置权限授权配置还是在上面的SecurityConfig类中只不过是在下面的那个方法中配置。packagecom.example.demo.security;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;importcom.example.demo.entity.Result;importcom.example.demo.security.userdetails.UserDetailsServiceImpl;importcn.hutool.json.JSONUtil;/** * SpringSecurity配置 */ConfigurationEnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{// 注入服务类AutowiredprivateUserDetailsServiceImpluserDetailsServiceImpl;/** * 1. 认证配置 */Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsServiceImpl);}// 设置密码加密方法BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}/** * 2. 授权的配置方法下面再讲先空着 */Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{// 1. 登陆登出设置http// 允许表单登陆.formLogin()// 自定义登陆页面注意action提交地址 和 账号密码表单name// .loginPage(/login.html)// 自定义后端登陆地址security默认的是/login// .loginProcessingUrl(/doLogin)// 自定义登陆成功后的处理前后端分离一般返回json数据.successHandler(newMyAuthenticationSuccessHandler())// 自定义登陆失败后的处理前后端分离一般返回json数据.failureHandler(newMyAuthenticationFailureHandler()).and().logout()// 自定义退出地址// .logoutUrl(/logout)// 退出成功后的处理.logoutSuccessHandler((req,res,aut)-{res.setContentType(application/json;charsetutf-8);ResultStringresultnewResult();result.setStatus(1);result.setCode(200);result.setMsg(退出成功);res.getWriter().write(JSONUtil.toJsonStr(result));})//使得session失效默认true// .invalidateHttpSession(true)//清除认证信息默认true// .clearAuthentication(true)//删除指定的cookie// .deleteCookies(cookie01);// 2. 跨域问题http.csrf().disable();// 3. 权限设置http// 对url进行访问权限控制.authorizeRequests()// 按角色来控制权限的.antMatchers(/test/t2).hasRole(ADMIN).antMatchers(/admin1/**,/admin2/**).hasAnyRole(ADMIN1,ADMIN2)// 按Authority有权限才能访问.antMatchers(/user/**).hasAuthority(/u/a).antMatchers(/test/t1).hasAuthority(/test/t1)// 直接放行的.antMatchers(/app/**).permitAll()// 其他任何请求都需要登陆.anyRequest().authenticated();}}3.2.2 注解表达式配置权限SpringSecurity的权限注解有5个都是用在方法上的分别是Secured检查指定的角色权限角色要加前缀ROLE_可以多个如Secured({ROLE_A, ROLE_B})PreAuthorize方法执行前进行权限检查一般都是用这个。用法PreAuthorize(hasRole(admin))、PreAuthorize(hasAuthority(/t1) and hasAuthority(/t2))、PreAuthorize(hasAnyRole(root,admin))PostAuthorize方法执行后进行权限检查还没用过PreFilter过滤函数未用过PostFilter过滤函数未用过注意要想使用注解需要开启注解在security的配置类加上EnableGlobalMethodSecurity(prePostEnabled true, securedEnabled true)开启注解注释掉配置类方法中关于权限的配置。注解表达式使用如下packagecom.example.demo.controller;importjavax.servlet.http.HttpServletResponse;importorg.springframework.security.access.prepost.PreAuthorize;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;RestControllerRequestMapping(/test)publicclassTestController{GetMapping(/t1)publicStringtest1(String name,HttpServletResponse response){response.addHeader(userId,123);returnnamenull?zhangsan:name;}GetMapping(/t2)PreAuthorize(hasAnyRole(ROOT,ADMIN))publicStringtest2(){returntest2;}GetMapping(/t3)PreAuthorize(hasAuthority(/test/t3))publicStringtest3(){returntest3;}GetMapping(/t4)PreAuthorize(hasAuthority(/t4) and hasAuthority(/t5))publicStringtest4(){returntest4;}GetMapping(/t5)PreAuthorize(hasRole(admin))publicStringtest5(){returntest5;}}注解的判断方法走的是类org.springframework.security.access.expression.SecurityExpressionRoot中的可以看看逻辑。