文章目录
- 一、设置密码的两个核心接口:
- 二、设置用户名和密码的两种方式
- 三、基于角色和权限进行访问控制
- 四、Spring Security认证授权相关注解
- 五、用户注销
- 六、十天免登录(自动登录技术)
- 七、CSRF
- 八、到此为止完整代码如下
一、设置密码的两个核心接口:
1.UserDetailsService:查询数据库里的用户名和密码
我们如果要自己写校验用户名密码,我们需要继承UsernamePasswordAuthenticationFilter
,并重写它的attemptAuthentication
方法(校验过程),和它父类AbstractAuthenticationProcessingFilter
的successfulAuthentication
方法(校验成功怎么做)和unsuccessfulAuthentication
(校验失败怎么做)。
我们实际项目在做校验时,用户名和密码是从数据库里查的,这个就需要用到UserDetailsService接口。我们需要自己写个类实现UserDetailsService并重写其loadUserByUsername方法,编写查询数据库过程,返回User对象,这个User对象时SpringSecurity框架提供的。
2.PasswordEncoder 给密码加密
用于User返回对象里面的密码加密
二、设置用户名和密码的两种方式
1.自定义配置类来完成用户登录
1.改pom
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-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>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
2.写yml
随便写个端口防止冲突
server.port=8111
3.主启动类
@SpringBootApplication
public class Securitydemo1Application {
public static void main(String[] args) {
SpringApplication.run(Securitydemo1Application.class, args);
}
}
4.配置类
5.业务类
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String add(){
return "hello security";
}
}
6.测试
2.自定义实现类来完成用户登录
1.改pom
不变
2.写yml
不变
3.主启动类
不变
4.Service层
//继承UserDetailsService,重写里面的loadUserByUsername方法,方法里面设置好用户名、密码、权限
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");//人家规定权限是collection集合类型
return new User("marry", new BCryptPasswordEncoder().encode("123"), auths);//用户名+密码+权限
}
}
5.配置类
//继承WebSecurityConfigurerAdapter,重写里面的configure()方法,把你的实现类userDetailsService设置进去
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder( createPasswordEncoder());
}
@Bean
public PasswordEncoder createPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
6.Controller类
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String add(){
return "hello security";
}
}
7.测试
3.结合数据库完成用户登录
真实的案例中,肯定是查询数据库获得密码的
先准备好数据库
1.改pom
不变
2.写yml
server.port=8111
#spring2.2开始,默认使用的就是mysql8,那么mysql8都需要这样配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #mysql8的驱动中多个cj
spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=GMT%2B8 #mysql8的url中多了个时区serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
3.主启动类
@SpringBootApplication
@MapperScan("com.atguigu.securitydemo1.mapper") //加了MapperScan扫描mapper
public class Securitydemo1Application {
public static void main(String[] args) {
SpringApplication.run(Securitydemo1Application.class, args);
}
}
4.Service层
1.实体层
@Data
public class Users {
private Long id;
private String username;
private String passowrd;
}
2.mapper层
//BaseMapper<Users>是一个接口,里面有各种各样的操作数据库的方法,UserMapper只需要继承BaseMapper<Users>即可
//UserMapper是一个接口,理论上应该有一个实现类,实现类上面要有@Repository注解注入容器,但是可以不写实现类,那么@Repository注解就得加在这个接口上面
@Repository
public interface UserMapper extends BaseMapper<Users> {
}
3.service层
//继承UserDetailsService,重写里面的loadUserByUsername方法,方法里面设置好用户名、密码、权限
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
Users users = userMapper.selectOne(wrapper);
if(users == null){
throw new UsernameNotFoundException("用户名不存在");
}else{
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(username, new BCryptPasswordEncoder().encode(users.getPassowrd()), auths);
}
}
}
5.配置类(没有变)
//继承WebSecurityConfigurerAdapter,重写里面的configure()方法,把你的实现类userDetailsService设置进去
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder( createPasswordEncoder());
}
@Bean
public PasswordEncoder createPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
6.Controller类
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String add(){
return "hello security";
}
}
7.测试
4.自定义登陆页面+有的用户无需认证
1.改配置类
配置类同样是继承WebSecurityConfigurerAdapter
,但重写的方法还有configure(HttpSecurity http)
//继承WebSecurityConfigurerAdapter,重写里面的configure()方法,把你的实现类userDetailsService设置进去
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder( createPasswordEncoder());
}
@Bean
public PasswordEncoder createPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //自定义自己编写的登录页面
.loginPage("login.html") //设置登录页面是login.html
.loginProcessingUrl("/user/login") //用户在登陆页面输入登录信息后访问的路径是"/user/login",但是不需要我们到Controller中去写/user/login,SpringSecurity已经帮你做了
.defaultSuccessUrl("test/index").permitAll() //登录成功后跳转到的路径
.and().authorizeRequests().antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径不需要认证
.anyRequest().authenticated() //表示所有路径都能访问 无需认证
.and().csrf().disable(); //关闭csrf保护
}
}
2.改Controller
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String add(){
return "hello security";
}
@GetMapping("index")
public String index(){
return "hello index";
}
}
3.编写登录页
login.html,注意里面的username和password必须是这两个名字,不然spring security识别不到。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username">
<br>
密码:<input type="password" name="password">
<input type="submit" value="login">
</form>
</body>
</html>
4.测试
三、基于角色和权限进行访问控制
1.hasAuthority方法
1.修改配置类
只修改了圈主的地方
2.修改service
3.总说
- service层设置了凡是数据库查出的用户都具有admin权限,在配置类中规定只有拥有admin权限的才能访问,那么数据库中的人儿都可以访问。
- 如果service层设置了凡是数据库查出的用户都具有user权限,在配置类中规定只有拥有admin权限的才能访问,那么数据库中的人都不可以访问。
- hasAuthority方法针对某一个权限,如果是多个权限(管理员可以访问,普通用户也可以访问)就没有办法了
2.hasAuthority 方法
1.修改配置类
2.service
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("normal");//查出的用户具有normal权限
3.总说
- 如果当前的主体有任何提供的权限(给定的作为一个逗号分隔符的字符串列表)的话,返回true。假设有个路径管理员可以访问,普通用户都能访问,则用这个方法设置。
3.hasRole方法
- 如果用户具备给定角色就允许访问,否则403
1.配置类
2.UserDetailsService
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
3.总说
4.hasAnyRole方法
- 表示多角色的,用户具备任何一个条件都可以访问。
1.修改配置文件:
2.修改UserDetailsService
5.自定义 403(没有权限访问) 页面
1.修改配置类
2.创建403页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>没有访问的权限</h1>
</body>
</html>
3.测试
四、Spring Security认证授权相关注解
1.@Secured
判断是否具有某个角色
,另外需要注意这里匹配的字符串需要加前缀"ROLE_
",使用注解前要先开启注解功能
启动类上加@EnableGlobalMethodSecurity(securedEnabled = true)
1.修改主启动类
2.在Controller方法上添加注解
@GetMapping("update")
@Secured({"ROLE_sale","ROLE_manager"})
public String update(){
return "hello update";
}
3.测试
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
UserDetailsService中设置用户的权限、角色,因为设置了用户具有ROLE_sale角色,所以用户是可以访问到的/test/update的
2.@PreAuthorize
进入方法前进行权限
验证或角色
验证@PreAuthorize("hasAnyAuthority('admins')")
、@PreAuthorize("hasAuthority('admins')")
就是进行权限验证@PreAuthorize("hasAnyRole('Role_admin')")
、@PreAuthorize("hasRole('Role_admin')")
就是进行角色验证
启动类上加@EnableGlobalMethodSecurity(prePostEnabled = true)
1.启动类上
2.controller
@GetMapping("update")
@PreAuthorize("hasAnyAuthority('admins')")
public String update(){
return "hello update";
}
3.测试
用户拥有了admin权限,所以可以访问/test/update
3.@PostAuthorize
用到的不多,就不解释了,如果想看就找视频的p15
4.@PostFilter
用到的不多,就不解释了,如果想看就找视频的p15
5. @PreFilter
用到的不多,就不解释了,如果想看就找视频的p15
五、用户注销
1.改配置类
2.success.html
3.测试
六、十天免登录(自动登录技术)
- Cookie:我们以前是把用户登录信息存到cookie中,这样用户下次登录就不用再次输入密码什么的
- Spring Security:现在我们使用Spring Security安全框架机制也可以实现自动登录
1.Spring Security自动登陆的原理
第一次登陆时,会向浏览器
和数据库
中存信息(在浏览器中存入cookie的加密串,在数据库中存入cookie的加密串和用户信息的对应关系)
当第二次访问时,获取cookie中的信息,和数据库中的信息进行对比,如果查询到对应信息,认证成功,可以登录。
1.Spring Security自动登陆案例
1.数据库
JdbcTokenRepositoryImpl默认会帮我们创建这个表,其源码sql如下;
2.修改配置类
在原有配置类基础上把数据源注入,同时使用jdbcTokenRepository.setDataSource(dataSource)
把我们的数据源set进去;并且加入自动登陆的配置
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(true);//在启动时把表创建
return jdbcTokenRepository;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin()
.loginPage("/on.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/success.html").permitAll()
.failureUrl("/unauth.html")
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()
.antMatchers("/test/index").hasRole("sale")
//加入自动登陆的配置
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository())//把persistentTokenRepository对象设置进去
.tokenValiditySeconds(60)//设置有效时长,单位秒
.userDetailsService(userDetailsService);//把userDetailsService设置进去
.and().csrf().disable(); //关闭csrf防护
}
}
3.修改登录页面
4.测试
七、CSRF
1.对CSRF 理解
- 跨站请求伪造 CSRF , 是一种挟制用户在当前已登录的 Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。
- 跨站请求攻击:假如你在尚硅谷网站已经进行了认证,当你打开其他网站,那个网站就可以得到当前浏览器中所有的cookie信息,这就很不安全,这就是跨站请求攻击。
- 从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用程序,Spring Security CSRF 会针对 PATCH,POST(添加),PUT(修改) 和 DELETE(删除) 方法进行防护,针对这些对数据库修改的操作进行了保护。
2.开启CSRF的方法
1.修改配置类
2.修改登录页
3.说明事项
- 如果你在配置类中没有关闭CSRF防护,那么你必须在登录页添加那个隐藏域,要不然没有携带隐藏域的你会被当成是跨站请求而被禁止登录
八、到此为止完整代码如下
目录结构
1.pom
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-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>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.yml
server.port=8111
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
3.主启动类
@SpringBootApplication
@MapperScan("com.atguigu.securitydemo1.mapper")
@EnableGlobalMethodSecurity(securedEnabled=true)
public class Securitydemo1Application {
public static void main(String[] args) {
SpringApplication.run(Securitydemo1Application.class, args);
}
}
4.Service层(没有变)
1.实体层
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
private Integer id;
private String username;
private String password;
}
2.mapper层
@Repository
public interface UsersMapper extends BaseMapper<Users> {
}
3.service层
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper = new QueryWrapper();
wrapper.eq("username",username);
Users users = usersMapper.selectOne(wrapper);
if(users == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}
5.配置类
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//退出
http.logout().logoutUrl("/logout").
logoutSuccessUrl("/test/hello").permitAll();
//配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin() //自定义自己编写的登录页面
.loginPage("/on.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/success.html").permitAll() //登录成功之后,跳转路径
.failureUrl("/unauth.html")
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
//当前登录用户,只有具有admins权限才可以访问这个路径
//1 hasAuthority方法
// .antMatchers("/test/index").hasAuthority("admins")
//2 hasAnyAuthority方法
// .antMatchers("/test/index").hasAnyAuthority("admins,manager")
//3 hasRole方法 ROLE_sale
.antMatchers("/test/index").hasRole("sale")
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)//设置有效时长,单位秒
.userDetailsService(userDetailsService);
// .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// .and().csrf().disable(); //关闭csrf防护
}
}
6.Controller
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String hello() {
return "hello security";
}
@GetMapping("index")
public String index() {
return "hello index";
}
@GetMapping("update")
//@Secured({"ROLE_sale","ROLE_manager"})
//@PreAuthorize("hasAnyAuthority('admins')")
public String update() {
System.out.println("update......");
return "hello update";
}
}