1、springsecurity是什么
SpringSecutiy是咋们的Spring团队给我们开发的这样一个基于认证和授权的框架 简单的说 就是原来项目的认证(登陆) 和 授权是程序员自己编写代码来完成的 认证:用户的身份认证(登陆) 就是用户登陆成功之后 每一次访问其他的资源 这个时候 我们也需要做身份判断 授权:判断用户是否具有访问某一个方法的权利 也就是说我们在学习了 SpringSecurity之后 这个登陆 认证 授权的 功能就不能自己开发了 我们的SpringSecurity就帮助我们自动的实现了 SpringSecurity实际上 在开发过程中对 前后不分离的项目 支持的比较好 对我们前后分离的项目 支持的没有那么好 简单的说如果你的项目是前后分离的话 使用了SpringSecurity的话 那么还需要自定义很多的类 去完成我们的功能
2、SpringSecurity能干什么
用户登陆 用户身份认证 用户授权(鉴权) 常见的认证和授权的框架有哪些? shiro springsecurity 这两个框架的功能是一样的 那么这两个之间有啥区别呢? shiro支持在任何场景下使用 而且轻量级 springsecurity必须在Spring依赖的场景下使用 从使用场景来说 shiro 更加广泛的使用场景 springsecurity要相对弱一些 那么你们肯定有一个疑问? 那么为什么我们要学习springsecurity呢? shiro:这个是一个团队开发的 sprignsecurity是spring这个团队开发的 如果是你要使用shiro的话 那么所有的配置都需要自己去写 简单的说 springboot中是没有shiro的自动配置的 但是security就不一样了 所有的配置 springboot都帮你完成了.... 从学习的成本上说 shiro也更小 但是可能在spring的场景下 你要使用可能就更加麻烦一些 而且现在市场上的趋势就是使用security
3、SpringSecurity的基本使用
3.1、导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--生成getter和setter方法的地方-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
3.2、编写控制器代码
@RestController
public class UserController {
@RequestMapping("/test")
public Object test(){
return "你最帅";
}
}
3.3、编写启动类
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class,args);
}
}
3.4、编写配置类
@SpringBootConfiguration
@ComponentScan(basePackages = {"com.zuo"})
public class AppConfig {
}
3.5、测试
在浏览器上输入 http://127.0.0.1:8080/test
3.6、用户名和密码
4、SpringSecurity的运行原理是什么
SpringSecurity的底层原理 就是一些列的 过滤器 也就是说 只要你访问资源 那么就会执行到我们指定的过滤器中去 然后在过滤器中去完成用户需要的功能 我们说了 过滤器是为了规避Java中的 单一职责 那么什么是单一职责呢? 简单的说就是自己的事情 自己做 不要将所有的业务耦合到 一个业务中去 过滤器的底层是一个 责任链的设计模式 那么在SpringSecurity中有三个过滤器比较重要 FilterSecurityInterceptor: 这个过滤器的作用是一个方法级别的过滤器、这个过滤器位于责任链的最底层(简单的说 就是最后一个执行的过滤器)、这个过滤器的作用就是判断用户是否具有访问某一个方法的权限 ExceptionTranslationFilter: 这个过滤器的作用是:用来进行异常处理的这样一个过滤器。这个过滤器的作用是捕获认证或者授权产生的异常的、简单的说这个过滤器的作用是用来进行异常处理的 UsernamePasswordAuthenticationFilter: 这个过滤器的作用:用户身份认证的这样一个过滤器 简单的说就是进行用户的身份认证的、判断用户身份是否合法的 那么这个过滤器是怎么运行起来的呢?看我上图:
5、SpringSecurity在Web下的使用
5.1、设置用户名和密码的第一种方式
5.2、设置用户名和密码的第二种方式
package com.zuo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootConfiguration
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在这里你可以设置自己的用户名和密码
// 现在波波想问你们个问题:密码你们2阶段项目存储到数据库是怎么存储的?
/**
* MD5?是不是加密?
* 如果你们认为MD5是加密那么 你解密下?
* MD5不是加密:这个是一种散列算法 他还有一个名字叫做 文档摘要
* 他的设计初衷是为了验证文件在传输过程中 是否被人篡改过....
*
* 散列算法有一个特点:就是单向的 只能散列 不能还原 他为什么不能还原?
*
* 是因为他在进行散列的时候并不是对 这个字符串的每一位 都进行了散列 而是对这个字符串的某些位置进行了散列
* 最终求了一个散列的结果
*
* xiaobobotiedangousheng 0 2 5 7 9
*
* uhajdhasdjkhvfnfeifv
* 随意他不能还原
*
* 你们有没有想过一个问题:两个不同的字符传是不是取出来散列的位置恰好一样? 这个是有可能的
* 这个就称为 MD5的碰撞
*
* MD5碰撞如何解决呢?
* 1、增长字符串 减少出现碰撞的概率
* 2、在进行MD5散列之前 先进行SHA1散列 再进行MD5散列 减少MD5碰撞的概率
*
* 上面的问题说明一个原因 密码存储到数据库 不能是明文
*
*/
//我们要继续你给MD5的散列
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode("123");
log.info("密码散列之后的值是:"+encode);
auth.inMemoryAuthentication().withUser("tiedan").password(encode).roles("agent");
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
5.3、设置用户名和密码的第三种方式
5.3.1、编写查询数据库的类
@Component
public class MyUserDetailService implements UserDetailsService {
/**
* @param username:这个username实际上是前端页面传递过来的这个用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//拿到用户名 通过用户名去数据库查询用户对象?
//TODO 这个里面应该编写查询数据库的代码
if(!"xiaobobo".equals(username)){
throw new UsernameNotFoundException("用户名不对");
}
//程序执行到这里 说明用户名是对的 那么就应该返回 UserDetails的对象
//模拟从数据库查询出来了密码
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode("123");
//这个权限逻辑意义上是通过 用户id查询出来的用户权限或者角色
List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList("ROLE_admin", "ROLE_aunt", "user:query");
User user = new User("xiaobobo",encode,authorityList);
return user;
}
}
5.3.2、编写配置文件
@SpringBootConfiguration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService myUserDetailService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(myUserDetailService);
super.configure(http);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
6、自定义登陆页面
6.1、编写登陆页面
<form method="post" action="/login">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"> <br/>
<input type="submit" value="登陆">
</form>
6.2、编写配置文件
package com.zuo.config;
import com.zuo.service.impl.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootConfiguration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService myUserDetailService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(myUserDetailService);
http.formLogin()
.loginPage("/login.html") //这个是设置登陆页面的地址
.successForwardUrl("/test") //登陆之后的跳转地址
.loginProcessingUrl("/login") //这个随便设置的(登陆的表单提交地址)
.failureForwardUrl("/login.html") //登陆失败往login.html跳转
.usernameParameter("username") //表单中用户名的名字是什么
.passwordParameter("password"); //表单中密码的名字是啥
//还要进行页面的拦截设置
http.authorizeRequests()
.antMatchers("/login.html").permitAll() //这个页面不需要登陆就能访问
.anyRequest().authenticated(); //这个表示的是其他的任何请求都要认证之后才能访问
http.csrf().disable(); //关闭我们的csrf校验
super.configure(http);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
7、看下源码
8、用户退出功能的实现
8.1、编写退出页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/logout">退出</a>
</body>
</html>
8.2、编写配置文件
package com.zuo.config;
import com.zuo.service.impl.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootConfiguration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService myUserDetailService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(myUserDetailService);
http.logout()
.logoutUrl("/logout") //这个就是用户退出登陆的这个地址
.logoutSuccessUrl("/login.html"); //推迟登陆成功之后跳转的这个页面
http.formLogin()
.loginPage("/login.html") //这个是设置登陆页面的地址
.successForwardUrl("/test") //登陆之后的跳转地址
.loginProcessingUrl("/login") //这个随便设置的(登陆的表单提交地址)
.failureForwardUrl("/login.html") //登陆失败往login.html跳转
.usernameParameter("username") //表单中用户名的名字是什么
.passwordParameter("password"); //表单中密码的名字是啥
//还要进行页面的拦截设置
http.authorizeRequests()
.antMatchers("/login.html").permitAll() //这个页面不需要登陆就能访问
.anyRequest().authenticated(); //这个表示的是其他的任何请求都要认证之后才能访问
http.csrf().disable(); //关闭我们的csrf校验
super.configure(http);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
9、基于角色和权限的配置
9.1、使用代码的方式实现角色和权限的控制
9.1.1、编写控制器方法
/**
* 测试基于权限的控制(配置文件实现该功能)
* @return
*/
@RequestMapping("/test1")
public Object test1(){
return "你是最帅的";
}
/**
* 测试基于角色的控制(配置文件实现该功能)
* @return
*/
@RequestMapping("/test2")
public Object test2(){
return "真奇怪";
}
9.1.2、编写配置
.antMatchers("/test1").hasAuthority("user:delete") //这个表示的是test1的这个地址需要 user:delete这个权限才能访问
.antMatchers("/test2").hasRole("teacher")
//具有其中一个角色就能访问
// .antMatchers("/test2").hasAnyRole()
//具有任意的一个权限就能访问
// .antMatchers("/test2").hasAnyAuthority()
.anyRequest().authenticated(); //这个表示的是其他的任何请求都要认证之后才能访问
9.1.3、编写友好页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>大哥你没有权限访问这个地址</h2>
</body>
</html>
9.2、使用注解的形式实现方法的角色和权限的控制
9.2.1、开启我们security的注解功能
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
9.2.2、编写控制器的方法
/**
* 判断用户是否具有某一个角色 然后来访问这个资源
* @return
*/
@Secured({"ROLE_teacher","ROLE_admin"}) //一个成立就成立
@RequestMapping("/test5")
public Object test5(){
System.out.println("执行了这个代码----");
return "yes";
}
10、记住我的这个功能的实现
10.1、打开这个记住我的这个功能
//打开记住我的这个功能
http.rememberMe().userDetailsService(myUserDetailService)
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(2592000);
10.2、导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--这个包就是mybatis-plus的包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
10.3、编写项目的全局配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql:///xxx
10.4、创建数据库和表
CREATE TABLE `persistent_logins` (
`username` VARCHAR(64) NOT NULL,
`series` VARCHAR(64) NOT NULL,
`token` VARCHAR(64) NOT NULL,
`last_used` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
10.5、在登陆页面添加记住我的按钮
<form method="post" action="/login">
用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"> <br/>
记住我<input type="checkbox" name="remember-me"><br/>
<input type="submit" value="登陆">
</form>