最近有一个项目需要用到SpringSecurity安全框架,特意简单学习了一下,中间也踩了很多坑,在这里记录一下避免以后踩坑。
1、引入:
在很多的项目中,都会遇到认证问题,就是管理员与用户等不同角色访问页面权限的问题,以及数据库密码加密问题,SpringSecurity能帮我们很好地解决这个问题,但它的作用要多得多,而且更深层,这里我只简单学习了一下。
2、导包:
要想使用框架,需要导入springsecurity的包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
第一个是引入框架而导的包,第二个则是因为sec:authorize在使用中,默认的springsecurity4是只支持2.0.9以下的springboot,所以要想使用的时候更好地支持我们导入这个包。
3、新建WebSecurityConfig配置类:
继承WebSecurityConfigurerAdapter类,之后我们就可以进行重写配置来自定义功能了,不要忘了加上配置注解
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的权限认
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
}
4、 自定义配置:
①拦截配置:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的权限认
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
//方法注解方式
@Override
protected void configure(HttpSecurity http) throws Exception {
//请求规则(放行URL路径)
http.authorizeRequests()
.antMatchers("/index/registerBef").permitAll()
.antMatchers("/index/register").permitAll();
//没有权限就进入登录界面,定制登录页
http.formLogin()
//自定义登录页面(需要URL路径)
.loginPage("/index/loginBef").usernameParameter("name").passwordParameter("password")
.loginProcessingUrl("/index/login")
.defaultSuccessUrl("/emp/list?page=1",true)
.failureUrl("/index/loginError")
.permitAll()
.and().authorizeRequests().anyRequest().authenticated()
.and().exceptionHandling().accessDeniedPage("/index/roleError")
.and().headers().frameOptions().sameOrigin()
.and().csrf().disable();
//注销
http.logout().logoutUrl("/index/logout");
http.rememberMe();
}
}
因为注册页面是不做拦截的,所以要放行两个URL,一个是加载页面的,一个是处理注册数据的。
如果不是放行的URL,那就要进入登录界面,利用http.formLogin属性可以自定义登录功能:
- loginPage(): 定义登录界面跳转的URL或者HTML,但是一般情况是不能直接访问HTML,而是通过controller来进行跳转的,所以一般这里配置跳转登录页面的URL。
- usernameParameter(): 定义登录用户名的名称,如果没有设置成username的话,springsecurity的loadUserByUsername是接收不了你前台定义的名称数据的,如果你想自定义设置,那么就需要在这里进行设置为前台上传的名称。
- passwordParameter(): 定义登录密码的名称,与上面同理,但是不是同一个地方进行接收的,但也要设置成相同才能接收。
- loginProcessingUrl(): 登录进程url,就是自定义登录页面时提交登录表单时的action地址,交给springsecurity来进行处理认证,不需要自己写登录处理认证方法。
- defaultSuccessUrl(): 默认登录成功后跳转的URL,这里可以自定义设置为自己想要跳转的第一个界面。
- failureUrl(): 登录认证失败跳转的URL,这里建议自己写一个对应的认证失败页面HTML来进行跳转,当然也是通过控制器来进行跳转。
- permitAll(): 对于所有人都应用这些规则。
- authorizeRequests().anyRequest().authenticated(): 设置其他所有的请求都需要验证。
- exceptionHandling().accessDeniedPage(): 设置权限验证失败后跳转的页面,这个权限验证是通过角色信息里的ROLE,springsecurity自行进行验证判断的。
- headers().frameOptions().sameOrigin(): 表示该页面可以在相同域名页面的 frame 中展示,多用于局部刷新。
- csrf().disable(): 关闭CSRF防护,因为从springsecurity4开始CSDF防护是默认开启的,默认会拦截请求,进行CSRF处理,作用是为了防止不是其他第三方网站访问,要求访问时数据携带参数名为_csdf值为token(token在服务端产生)的内容,如果token和服务器的token匹配成功,则正常访问。正常情况下,我们没必要用这种,所以要关闭的话,就设置csrf().disable()。
- logout().logoutUrl(): 设置注销时处理跳转的URL,会使springsecurity重新回到登录认证开始前的拦截状态。
- rememberMe(): 记住账号密码。
②配置拦截忽略文件夹:
@Override
public void configure(WebSecurity web) throws Exception{
// 设置拦截忽略文件夹,对资源放行
web.ignoring().antMatchers("/bootstrap/**", "/js/**","/css/**","/js/**","/fonts/**","/views/register.html","/views/login.html","/views/loginError.html","/views/RoleError.html");
}
web.ignoring().antMatchchers(): 设置拦截忽略文件夹,对资源放行,主要针对一些静态资源,或者是一些不想被拦截的html。
③配置密码加密策略:
//配置采用哪种密码加密算法
@Bean
public PasswordEncoder passwordEncoder() {
//不使用密码加密
//return NoOpPasswordEncoder.getInstance();
//使用默认的BCryptPasswordEncoder加密方案
return new BCryptPasswordEncoder();
//strength=10,即密钥的迭代次数(strength取值在4~31之间,默认为10)
//return new BCryptPasswordEncoder(10);
//利用工厂类PasswordEncoderFactories实现,工厂类内部采用的是委派密码编码方案.
//return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
有很多种加密策略,在注释中写的比较详细,一般会使用默认的BCryptPasswordEncoder加密策略。
④加入数据安全认证:
@Autowired
private UserDetailsServiceImpl userDetailsService;
// 加入自定义的安全认证:数据库用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
使用上述的密码策略进行加密,我们要从数据库中提取这个密码进来,或者对传入的密码进行加密,所以就要实现一个接口实现,这个接口实现是springsecurity自带的,但我们需要重写里面的loadUserByUsername方法。
5、重写loadUserBuUsername方法:
①新建UserDetailsService:
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
}
②重写loadUserByUsername方法:
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
// 从数据库中取出用户信息
Power user = userService.findByUsername(name);
if (user == null) { // 判断用户是否存在
throw new UsernameNotFoundException("用户名不存在");
}
// 添加角色
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(user.getRole_code()));
return new User(user.getUser_name(), user.getUser_password(), authorities); // 返回UserDetails实现类
}
}
先用前台登录页面上传的username,从数据库中将user对象取出来,如果用户不存在,则直接报异常,用户名不存在。如果存在那么就将角色权限添加进GrantedAuthority集合里面,可以添加很多权限进去,可以通过遍历添加,之后将数据库用户名、密码、权限集合进行返回,使用安全框架的密码验证明文密码是否与数据库对应,如果对应则安全验证通过,否则不通过。
6、权限验证方法:
权限是属于角色的,命名方式可以是ROLE_**,也可以是**,对于第一种最后在前台的验证方式这两种都可以成功,对于第二种则只能用第二种的验证名称。
例如:前端使用
<th:block sec:authorize="${hasAnyRole('ADMIN','MANAGER')}">
<a th:href="@{/user/list}" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-user" aria-hidden="true"/> 角色界面</a>
</th:block>
<th:block sec:authorize="${hasAnyRole('ADMIN','MANAGER')}">
<a th:href="@{/dep/list}" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-user" aria-hidden="true"/> 部门界面</a>
</th:block>
<th:block sec:authorize="${hasAnyRole('EMPLOYEE','ADMIN','MANAGER')}">
<a th:href="@{/emp/list(page=1)}" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-user" aria-hidden="true"/> 员工界面</a>
</th:block>
<th:block sec:authorize="${hasRole('ADMIN')}">
<a th:href="@{/role/list}" type="button" class="btn btn-primary"><span class="glyphicon glyphicon-user" aria-hidden="true"/>权限界面</a>
</th:block>
hasRole和hasAnyRole方法可以对当前角色的权限进行验证,如果有相应的权限,则该页面会显示,这也是前面我们导入第二个包的原因,不然sec:authorize不会生效。
当然后端也可以使用,如果出现权限不允许的情况,后端就会加载到RoleError界面去:
@RequestMapping("dep")
@Controller
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
public class DepartmentController {
}
假如是权限为ROLE_EMPLOYEE的员工角色进行访问,则会访问失败,跳转权限不足界面。
而权限为ROLE_ADMIN的管理员和ROLE_MANAGER的经理角色,那就会成功加载相应界面。
自此,springsecurity的基本使用就可以正常进行,还有更深层的作用之后会继续进行学习。