Day46_Spring Security—Spring Security的Web使用

一、设置密码的两个核心接口:

1.UserDetailsService:查询数据库里的用户名和密码

我们如果要自己写校验用户名密码,我们需要继承UsernamePasswordAuthenticationFilter,并重写它的attemptAuthentication方法(校验过程),和它父类AbstractAuthenticationProcessingFiltersuccessfulAuthentication方法(校验成功怎么做)和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";
    }
    
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乘风破浪的牛马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值