SpringSecurity是一个强大且高度可定制的身份验证和访问控制框架。
一、写一个案例1.构建项目引入web和security依赖
2.写一个登陆的controller
@Controller
public class SerurityController {
@RequestMapping("/mlogin")
public String login(){
System.out.println("执行了登陆功能");
return "redirect:main.html";
}
}
3.静态页面
4.直接启动访问项目ip端口,发现这并不是我们的login
5.使用默认用户名密码登陆
发现这才是熟悉的页面
6.访问接口进入断点
二、自定义认证 a.UserDetailsService要想使用springsecurity本质就是自定义一下认证逻辑,代码角度就是实现一个接口。
可以看见该接口里面有一个接口
public interface UserDetailsService {
// username是前端传过来的
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
返回值UserDetails也是一个接口
public interface UserDetails extends Serializable
实际返回中应该是该接口的实现类
public class User implements UserDetails, CredentialsContainerb.PasswordEncoder
//密码的加密和比较的方法 String encode(CharSequence rawPassword); boolean matches(CharSequence rawPassword, String encodedPassword);
实现类
只能加密不能解密
public class BCryptPasswordEncoder implements PasswordEncoder
Demo
@Test
void passwordEncoderDemo() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123456");
System.out.println("加密后:"+encode);
boolean matches = passwordEncoder.matches("123456",encode);
System.out.println("是否匹配:"+matches);
}
c.自定义逻辑的实现
注:在自定义逻辑前,SpringSerurity要求IOC容器内必须有一个密码解析器,所以需要先配置密码解析器。
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
接着实现UserDetailsServices
package com.example.springseruitydemo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String myusername) throws UsernameNotFoundException {
//1.查询数据库,不存在则抛出用户不存在异常
if (!"admin".equals(myusername)){
throw new UsernameNotFoundException("用户不存在");//1.模拟根据用户名从数据库中查询数据
}
//2.把查询出的密码(已加密)进行解析,或者直接吗密码放入构造方法
String encode = passwordEncoder.encode("123456");//2.模拟数据库中的密码
List authorityList = AuthorityUtils.createAuthorityList("admin", "normal");//3.模拟数据库中的权限
return new User(myusername,encode,authorityList);
}
}
可以看见现在登陆已使用自定义登陆逻辑
d.自定义登陆页面现在访问其他页面都会进行认证校验,跳转到登陆页面
@Configuration
public class PasswordEncoderConfig extends WebSecurityConfigurerAdapter {
//表单提交
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登陆页面
http.formLogin()
//1发现是这个请求地址的时候走拦截逻辑
//1.1controller可以不写,post请求即可,这里就相当于一个
.loginProcessingUrl("/mlogin")
//2指定登陆的页面
.loginPage("/login_page/login.html")
//3页面跳转必须是post
.successForwardUrl("/toMain")
//4登陆失败也是一个post请求
.failureForwardUrl("/toError");
//授权认证拦截
http.authorizeHttpRequests()
//1不能拦截登陆页面和登陆失败页面
.antMatchers(
"/login_page/login.html",
"/error.html")
.permitAll()
//2.拦截所有请求
.anyRequest().authenticated();
//关闭csrf防火墙
http.csrf().disable();
}
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
e.自定义登陆参数
现在回顾之前的配置流程,拦截请求->页面发送请求->框架拦截校验,其中页面拦截的资源路径我们不用写controller 只需要和前端约定即可,这个是由我们来自定义的。但是post请求方式和两个参数username和password则有框架约定写死,该如何自定义呢?
public class UsernamePasswordAuthenticationFilter
看到这里就明白了只需要设置一下就可以了,另外需要和前端请求页面参数一致。
d.自定义校验结果处理现在流行的是前后端分离开发,所以由后端来进行跳转成功失败页面是不合理的,所以要自定义一下登陆结果的处理。其实就是研究一下这两个方法。
先看成功
public FormLoginConfigurersuccessForwardUrl(String forwardUrl) { successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl)); return this; }
public ForwardAuthenticationSuccessHandler(String forwardUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> "'" + forwardUrl + "' is not a valid forward URL");
this.forwardUrl = forwardUrl;
}
自定义步骤
1.继承AuthenticationSuccessHandler
2.重写方法onAuthenticationSuccess
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String successUrl;
public MyAuthenticationSuccessHandler(String successUrl) {
this.successUrl = successUrl;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
User user = (User)authentication.getPrincipal();
response.sendRedirect(successUrl);
}
}
3.注释.successForwardUrl
4.指定处理器.successHandler(new MyAuthenticationSuccessHandler(“http://www.baidu.com”))
失败处理器同理
三、参数详解 a.anyRequest()拦截除permitAll()的所有请求
b.antMatchers支持通配符
?:匹配一个字符
*:匹配0个或多个字符
**:匹配零个或多个目录
**案例:**放行静态资源
配置放行的静态资源
c.regexMathcers使用正则表达式来放行
regexMathcers(“正则表达式”).permitAll()
注:
所有的认证匹配方法均有两个参数的重载方法,可以用于请求方式的校验。
当我们把controller换成post请求时候,返回请求方式不允许。
现在再去访问,则拦截。因为浏览器发送的是 get请求 和放行的不一致
四、权限访问案例:会员,VIP
.antMatchers("/main2.html").hasAuthority("admin")//验证是否有该权限
.antMatchers("/main2.html").hasAnyAuthority("admin","abb")//验证是否有多个权限
该权限和自定义登陆时候返回的保持一致,否则会报错
注:但是这里需要注意的是:hasAnyAuthority(“admin”,“abb”)当用户有一个权限负荷要求的时候,便认为有权限来进行操作。
五、角色判断只需要在自定义登陆逻辑中返回User即可,而且角色和权限放在一起,但是需要按照规则写,ROLE_开头
同样的可以配置角色权限限制
.antMatchers("/main2.html").hasRole("worker")
当然你可以配置多个角色有该权限操作
.antMatchers("/main2.html").hasAnyRole("worker")
六、IP限制
目前没有在版本中找到
七、自定义未授权1.继承AccessDeniedHandler,重写控制方法
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.sendRedirect("/403.html");
}
}
2.配置HttpSecurity未授权处理bean
//自定义403处理 http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);八、基于access的认证
前面无论是基于权限还是基于角色都是通过access方法来实现认证的。
待补充。。。



