SpringBoot集成SpringSecurity小结
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
SpringSecurity最重要的两个 概念就是认证和授权 下面分开来写
认证
认证主要解决的问题就是 识别出用户 解决“他是谁“ 的问题 在springSecurity的实现逻辑如下
SpringSecurity有一个接口UserDetailService就是专门来实现用户认证的 当我们需要自定义自己的认证逻辑时 就需要继承这个接口 并且注入
源码如下
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
简单来说 登录的时候 系统会自动调用这个方法 LoadUserByUsername 这里主要是通过数据库 通过前端传来的username 来查询有无对应的用户 如果有的话就返回一个UserDetails对象 返回对象之后 会在内部进行认证(密码/盐/加密过密码等) 这个UserDetails也是个借口 spring默认提供的实现类是User
另外 密码需要加密 就需要用到一个密码解析器 接口是(PasswordEncoder) 这里官方推荐实现类是BCryptPasswordEncoder 需要先注入到Spring容器统一管理,数据库中存储的密码都是通过密码解析器加密的密文,
注:PasswordEncoder是一个密码加密接口,而BCryptPasswordEncoder是Spring Security提供的一个实现方法,我们也可以自己实现PasswordEncoder。 不过Spring Security实现的BCryptPasswordEncoder已经足够强大,它对相同的密码进行加密后可以生成不同的结果
具体的注入方法就是 新建一个配置类 注入即可
@Configuration
public class ... {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
...
}
有了密码解析器 loadUserByUsername的逻辑就可以完成了
@Configuration
public class UserDetailService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 模拟一个用户,实际项目中应为: 根据用户名查找数据库,如果没有记录则会返回null,有则返回UserDetails对象 简而言之 下面这两行我们应该是在数据库中查询 这里我假设user就是我查询出来的数据 当然了 数据库中的密码肯定是加密过的这里就手动加密一下来模拟数据库的过程
MyUser user = new MyUser();
user.setUserName(username);
user.setPassword(this.passwordEncoder.encode("123456"));
// 输出加密后的密码
System.out.println(user.getPassword());
// 返回对象之后 会在内部进行认证(密码/盐/加密过密码等)
return new User(username, user.getPassword(), user.isEnabled(),
user.isAccountNonExpired(), user.isCredentialsNonExpired(),
user.isAccountNonLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
AuthorityUtils.commaSeparatedStringToAuthorityList("admin")
这一句是为了生成一个权限列表 理论上这个也是应该从数据库中查询出来的 但是这里还没有讲到授权的概念 所以就用一个工具类进行模拟了
自定义登录页面
登录页面的表单
<form method="post" action="/login">
<label>用户名</label>
<input type="text" name="username">
<br>
<label>密码</label>
<input type="password" name="password">
<br>
<input type="submit" value="登录">
</form>
登录页的必须这样写 也就是说name=username name=password 必须这样写 如果一定要修改自己的name 的参数名
就需要在下面的formlogin()加上这几句
http.formLogin()
.usernameParameter("username123")
.passwordParameter("password123")
这时候表单的name=“...” 就需要对应起来了
自定义登录也面和授权都是通过继承一个配置类WebSecurityConfigurerAdapter
来实现的
重写下面的这个方法
protected void configure(HttpSecurity http)
自定义登录页的主要代码如下
http.formLogin()
.loginPage("/loginPage")
.loginProcessingUrl("/login")
.successForwardUrl("/login")
.failureForwardUrl("/loginFailPage");
http.authorizeRequests().antMatchers("/loginPage","/loginFailPage").permitAll();
http.authorizeRequests().antMatchers("/").authenticated();
//简单的理解为防火墙 防止跨站请求伪造
http.csrf().disable();
- loginPage 是指定登录页 这里可以是一个请求也可以是一个页面 由于我在controller中写的/loginPage将其映射到了一个表单页面 所以这样写
- loginProcessingUrl 表示处理登录的请求的URL 也就是登录表单对应的action 这里这个参数必须要和 表单的action一致
- successForwardUrl 表示登录成功后要跳转到哪里 这里必须提交一个post请求 假设我们直接填一个页面的地址 也就是***.html之类的 就会报405ERROR request not allowed
- failureForwardUrl 登录失败后跳转的页面 同样 的也需要加入到下面的放行中
指定了上面这些还不太够 还有一个问题 默认的情况下 我们的这些自定义的登录页面是被拦截的 我们需要先给登录的那些页面那些请求放行 于是就有下面的两行 登录成功实现跳转的那个请求/login
不需要加入放行 因为login请求是在登录成功之后才发起的
http.authorizeRequests().antMatchers("/loginPage","/loginFailPage").permitAll();
http.authorizeRequests().antMatchers("/").authenticated();
这里放行了两个请求/loginPage 和/login 下面一句是拦截/ 也就是直接访问主网站 会被请求授权 authenticated() 表示需要登录 也就是会自动跳转到登录页面
当然了 有了上面的这些还是不行 SpringSecurity还有自带的防止跨站请求伪造 我们进行这些简单的配置还是无法达到演示效果 所以需要加上一句
http.csrf().disable();
这个属于另一部分的内容 暂时先略过不说
自定义 登录成功(失败)处理器
实际应用中 都是前后端分离的 页面跳转都是前端控制的 所以需要实现自己的处理器 这里实现成功的处理器 通过实现接口来实现
/**
自定义登录成功处理器
*/
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler
{
private String url;
public MyAuthenticationSuccessHandler(String url)
{
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException
{
response.sendRedirect(url);
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException
{
httpServletResponse.sendRedirect(url);
}
}
这里是简单实现 把成功之后的页面重定向到百度的主页
登录失败的处理器也是同理的
授权
授权和拦截器很类似 但是比拦截器的方法多一点
SpringSecurity的授权是有先后顺序的 不能设置AnyRequests在antMatchers后面 总而言之也就是笼统的授权 放在后面 特定的放在前面 一般的 anyRequest都是放在最后面
-
antMatcher()
方法定义如下
public C antMatchers(String... antPatterns)
参数是不定项参数 每一个参数是一个ant表达式 用于匹配URL规则
规则如下
?
:匹配一个字符*
:匹配0个或多个字符**
匹配0个或者多个目录
在实际项目中 经常需要放行所有静态资源 下面演示放行js文件夹内所有的脚本文件
.antMatchers("/js/**","/css/**").permitAll()
还有一种方式是只要是js文件就都放行
.antMatchers("/**/*.js").permitAll()
-
regexMatchers()
正则表达式的匹配 用法和上面的一样 只是ant表达式换成了正则表达式就行了 另外 antMatchers() ,regexMatchers() 还可以有一个入参 来控制 对应的请求的方式的放行方式 例如只放行/demo 的post请求 那就是
.regexMatchers(HttpMethor.POST,"/demo").permitAll
-
mvcMatchers()
这个方法可以指定servletPath 例如项目的servletPath(也就是整个项目访问的前缀) 是xxxx 我要放行/demo
mvcMatchers("/demo").servletPath("/xxxx").permitAll();
需要注意的 角色必须是大写ROLE_开头 就是在UserDetails里面必须加 ROLE_ 但是在hasRole() 这一系列的方法中 不需要加ROLE前缀
以上都是基于权限或者是基于角色的授权 还有一种基于IP地址的,也就是...................
注解实现授权
除了使用配置类外 还可以使用注解 步骤如下
首先开启注解
@EnableGlobalMethodSecurity(securedEnabled = true ,prePostEnabled = true)
放在启动类上
securedEnabled = true表示开启@Seured注解
prePostEnabled = true 表示开启两个
-
@PreAuthorize
表示方法执行之前判断权限 参数填入access字符串就可以了
-
@PostAuthorize
表示方法执行之后判断权限 极少用
这些注解加在Controller对应的方法上控制权限
tips
: @Secured较老 不建议使用 可读性差