SpringSecurity学习汇总
在使用SpringSecurity时,首先需要添加Spring Security依赖,添加了这个依赖,就默认开启了认证授权。依赖如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
使用了这个依赖后,访问页面,就会默认跳到框架给的登录页面(当然也可以自己设置):http://localhost:8080/login。当你对SpringSecurity什么都没有配置的时候,默认用户username 是user,密码会在控制台生成(一个临时的uuid)
1. 自定义登录
1.1单个用户登录
在application.yml配置里一个用户
spring:
security:
user:
name: zhangSan
password: 123456
1.2多个用户登录
用户登录,密码需要加密,转成密文形式
使用User.builder()来构建UserDetails对象
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
// 配置用户方式一
UserDetails userDetails = User.builder()
.username("liSi")
.password(passwordEncoder().encode("123456")) // 使用加密器加密
.build(); // 推荐使用构造器模式
// 配置用户方式二,注意此时需要加一个权限值
UserDetails userDetails1 = new User("wangWu", passwordEncoder().encode("123456"), AuthorityUtils.NO_AUTHORITIES);
return new InMemoryUserDetailsManager(userDetails, userDetails1);
}
}
1.3DB登录
因为数据库的数据时密文的,所以不需要进行加密
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TUser tUser = tUserMapper.selectByName(username);
UserDetails userDetails = User.builder()
.username(tUser.getUsername())
.password(tUser.getPassword())
.build();
return userDetails;
}
};
}
2.自定义登录页
注意:在实际开发中跨站请求伪造的验证尽量保持开启状态,保证在进行敏感操作时的安全性
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.formLogin((formLogin)->formLogin.loginPage("/toLogin") // 登录跳转到该地址
.loginProcessingUrl("/index")
.successForwardUrl("/api/hello") // 登录成功跳转到/api/hello地址,采用post请求
)
.authorizeHttpRequests((authorize)->authorize
.requestMatchers("/toLogin", "/test").permitAll() // 这两个地址不用登录就能访问
.anyRequest().authenticated() // 任何请求都需要认证(登录)才能访问
)
.csrf((csrf)->{
csrf.disable(); // 禁用跨站请求伪造的验证
})
.build();
}
3.登录时进行验证码验证
生成一个验证码
@Controller
public class CaptchaController {
@GetMapping(value = "/api/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) {
// ICaptcha iCaptcha = CaptchaUtil.createCircleCaptcha(100, 25, 4, 16);
// ICaptcha iCaptcha = CaptchaUtil.createLineCaptcha(100, 25, 4, 16);
// ICaptcha iCaptcha = CaptchaUtil.createGifCaptcha(100, 25, 3);
// 生成干扰线的图形验证码
ICaptcha iCaptcha = CaptchaUtil.createShearCaptcha(100, 25, 4, 2);
// 把验证码放入session
request.getSession().setAttribute("captcha", iCaptcha.getCode());
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
// 把验证码写出到浏览器客户端
iCaptcha.write(outputStream);
// 刷新输出流的缓冲区
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
从服务器端引用验证码到客户端
<!--跳转地址需要和指定登录的处理地址一样-->
<form th:action="@{ '/index' }" method="post">
用户姓名:<input name="loginAct"><br>
用户密码:<input type="password" name="loginPwd"><br>
<input name="captcha">
<img id="captchaCode" th:src="@{ '/api/captcha' }" style="cursor: pointer" onclick="reImg()"><br>
<input type="submit" value="登录">
</form>
// 点击验证码后进行验证码刷新
<script th:inline="javascript">
function reImg() {
var img = document.getElementById("captchaCode");
img.src = "/api/captcha?random=" + Math.random();
}
</script>
自定义对图形进行验证的Filter
对输入的验证码进行验证
@Component
public class CaptchaFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("---------------" + request.getRequestURI());
if (request.getRequestURI().equals("/index")) {
String captcha = request.getParameter("captcha");
if (!StringUtils.hasText(captcha)) {
throw new RuntimeException("验证码为空");
}
String sessionCaptcha = (String) request.getSession().getAttribute("captcha");
if (!captcha.equalsIgnoreCase(sessionCaptcha)) {
throw new RuntimeException("验证码不匹配");
}
filterChain.doFilter(request, response);
} else {
filterChain.doFilter(request, response);
}
}
}
在 Spring Security 的过滤器链中添加一个自定义的过滤器 captchaFilter,并将其放置在 UsernamePasswordAuthenticationFilter 类之前。
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.formLogin((formLogin)->formLogin.loginPage("/toLogin") // 登录跳转到该地址
.loginProcessingUrl("/index") // 指定登录的处理地址
.successForwardUrl("/api/hello") // 登录成功跳转到/api/hello地址,采用post请求
)
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
4.登录时进行身份验证
4.1:基于角色的权限管理
方式一:直接在SecurityFilterChain 配置
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
// 弹窗的登录框
.httpBasic(httpBasis->{})
.authorizeHttpRequests(authorize->
authorize
.requestMatchers("/api/add").hasAnyRole("admin", "dev")
.requestMatchers("/api/delete").hasRole("admin")
.requestMatchers("/api/update").hasAnyRole("admin", "dev")
.requestMatchers("/api/query").hasAnyRole("admin", "dev", "test")
.anyRequest().authenticated() // 其他所有的请求都需要登录才能进行
)
// 没有权限访问时,跳转到403.html页面
.exceptionHandling(exception->
exception.accessDeniedPage("/403.html")
)
.build();
}
requestMatchers(“/api/add”).hasAnyRole(“admin”, “dev”)
这行代码表示admin用户和dev用户有权限访问"默认地址//api/add"页面,其他用户都没有权限访问这个页面
方式二:使用注解的形式配置
@RestController
public class RoleController {
@GetMapping(value = "/")
public String hello() {
return "欢迎登录";
}
@PreAuthorize(value = "hasAnyRole('admin', 'dev')")
@RequestMapping(value = "/api/add")
public String add() {
return "add";
}
@PreAuthorize(value = "hasRole('admin')")
@RequestMapping(value = "/api/delete")
public String delete() {
return "delete";
}
@PreAuthorize(value = "hasAnyRole('admin', 'dev')")
@RequestMapping(value = "/api/update")
public String update() {
return "update";
}
@PreAuthorize(value = "hasAnyRole('admin', 'dev', 'test')")
@RequestMapping(value = "/api/query")
public String select() {
return "query";
}
}
4.2基于资源的权限认证
方式一:直接在SecurityFilterChain 配置
@Bean
public UserDetailsService detailsService(PasswordEncoder passwordEncoder) {
UserDetails adminUser = User.builder()
.username("admin")
.password(passwordEncoder.encode("123456"))
.authorities("user:add", "user:delete", "user:update", "user:query") // 配置用户的权限标识符
.build();
UserDetails devUser = User.builder()
.username("dev")
.password(passwordEncoder().encode("123456"))
.authorities("user:add", "user:update", "user:query")
.build();
UserDetails testUser = User.builder()
.username("test")
.password(passwordEncoder().encode("123456"))
.authorities("user:query")
.build();
return new InMemoryUserDetailsManager(adminUser, devUser, testUser);
}
authorities(“user:add”, “user:delete”, “user:update”, “user:query”)
这行代码表示,用户admin有权限访问上面四个地址
方式二:使用注解的形式配置
@RestController
public class ResourceController {
@GetMapping(value = "/")
public String hello() {
return "欢迎登录";
}
@GetMapping(value = "/api/add")
@PreAuthorize(value = "hasAuthority('user:add')")
public String add() {
return "add";
}
@GetMapping(value = "/api/delete")
@PreAuthorize(value = "hasAuthority('user:delete')")
public String delete() {
return "delete";
}
@GetMapping(value = "/api/update")
@PreAuthorize(value = "hasAuthority('user:update')")
public String update() {
return "update";
}
@GetMapping(value = "/api/query")
@PreAuthorize(value = "hasAuthority('user:query')")
public String query() {
return "query";
}
}
5.获取当前登录用户的信息的四种方式
@RequestMapping(value = "/api/main")
public String main(Model model, Principal principal) {
model.addAttribute("principal", principal);
return principal;
}
@RequestMapping(value = "/api/main2")
public @ResponseBody Object main2(Authentication authentication) {
return authentication;
}
@RequestMapping(value = "/api/main3")
public @ResponseBody Object main3(UsernamePasswordAuthenticationToken authenticationToken) {
return authenticationToken;
}
@RequestMapping(value = "/api/main4")
public @ResponseBody Object main4() {
//从spring security的上下文环境中拿到当前登录人的信息对象
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication;
}