SpringSecurity

Spring Security 简介

Spring 是非常流行和成功的java开发框架,Spring Security正是Spring 家族中的成员。Spring Security基于Spring框架,提供一套Web应用安全性的解决方案。

正如您可能知道的关于安全方面的两个区域 “认证"和"授权”(或者访问控制),一般来说,Web应用的安全性包括 **用户认证(Authentication) 和 用户授权(Authorization)**两个部分,这两点也是Spring Security重要核心功能。

(1)用户认证指的是: 验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色对应一系列权限。通俗点讲就是系统判断用户是否有权限去做某些事情

入门案例

  1. 创建一个Springboot工程

  2. 改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>
    </dependencies>
    
  3. 创建一个controller

    @RestController
    @RequestMapping("/login")
    public class LoginController {
        @GetMapping("hello")
        public String hello(){
            return "Hello world";
        }
    }
    

4.测试

Spring Security基本原理

Spring Security 本质是一个过滤器链

FileterSecurityInterceptor:方法级的权限过滤器,基于过滤器的最底部

ExceptionTranslationFilter:判断授权过程中的异常

UsernamePasswordAuthenticationFilter: 校验用户名和密码

Spring Security过滤器是如何加载的?

Spring boot对于Spring Security提供了自动化配置方案:

使用SpringSecurity配置过滤器

DelegatingFilterProxy

UseDetailsService接口

用于查询数据库用户名和密码

创建一个类 继承UsernamePasswordAuthenticationFilter,重写三个方法

创建类 实现UseDetailsService,编写查询数据过程,返回User对象

这个User对象是安全框架提供的对象

PasswordEncoder接口

对密码进行加密

WEB权限方案

如何设置登录的用户名和密码

第一种方式:通过配置application文件进行配置

spring.security.user.name=admin
spring.security.user.password=admin

第二种范式:创建配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String password = encoder.encode("admin");
        auth.inMemoryAuthentication().withUser("admin").password(password).roles("admin");
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

第三种方式:自定义编写实现类

登录,首先security框架会去配置文件和配置类中找 用户名和 密码 ,如果不存在,就会去找UserDetailService接口

  1. 创建配置类,设置使用哪个UserDetailService实现类

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    
  2. 编写实现类,返回User对象,User对象有用户名、密码和权限

    @Service("userDetailsService")
    public class MyUserDetailService implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            Collection<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
            User user = new User("admin",new BCryptPasswordEncoder().encode("admin"),role);
            return user;
        }
    }
    
  3. 测试

基于mybatis plus配置的方式登录

pom

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
    </dependency>
    <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>
</dependencies>

application

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/boot1?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=034312

entity

@Data
public class User {
    private Integer id;
    private String name;
    private String pwd;
}

mapper

@Repository
public interface UserMapper extends BaseMapper<User> {
}

config

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public  PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

service

@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<com.zhj.springsecuritydemo.entity.User> wrapper = new QueryWrapper<>();
        wrapper.eq("name",username);
        com.zhj.springsecuritydemo.entity.User user = userMapper.selectOne(wrapper);
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        return new User(user.getName()
                ,new BCryptPasswordEncoder().encode(user.getPwd())
                ,AuthorityUtils.commaSeparatedStringToAuthorityList("role"));
    }
}

启动类

@SpringBootApplication
@MapperScan("com.zhj.springsecuritydemo.mapper")
public class SpringsecuritydemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringsecuritydemoApplication.class, args);
    }

}

测试

自定义登录界面

config

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 自定义登录页面
                .loginPage("/login.html") //登录页面设置
                .loginProcessingUrl("/user/login") // 登录访问路径
                .defaultSuccessUrl("/login/index").permitAll() // 登录成功之后跳转路径
                .and().authorizeRequests()
                .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
                .anyRequest().authenticated()
                .and().csrf().disable();// 关闭csrf防护

    }
}

controller

@RestController
@RequestMapping("/login")
public class LoginController {
    @GetMapping("hello")
    public String hello(){
        return "Hello world";
    }

    @GetMapping("index")
    public String index(){
        return "login success";
    }


}

static

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<pre>
    <form action="/user/login" method="post">
       用户名: <input type="text" name="username"> <!-- name="username" 不允许变-->
        密码: <input type="password" name="password">  <!-- name="password" 不允许变-->
        <input type="submit" value="登录" >
    </form>
</pre>
</body>
</html>

权限设置

hasAuthority :当前主体具有制定的权限,则返回true,否则返回false

  1. 在配置类设置当前访问地址有哪些权限

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 自定义登录页面
                .loginPage("/login.html") //登录页面设置
                .loginProcessingUrl("/user/login") // 登录访问路径
                .defaultSuccessUrl("/login/index").permitAll() // 登录成功之后跳转路径
                .and().authorizeRequests()
                .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
                .antMatchers("/login/index").hasAuthority("admin")// 设置admin权限 -----------
                .anyRequest().authenticated()
                .and().csrf().disable();// 关闭csrf防护
    
    }
    
  2. 在userDetailService,返回user对象的权限

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<com.zhj.springsecuritydemo.entity.User> wrapper = new QueryWrapper<>();
        wrapper.eq("name",username);
        com.zhj.springsecuritydemo.entity.User user = userMapper.selectOne(wrapper);
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        return new User(user.getName()
                ,new BCryptPasswordEncoder().encode(user.getPwd())
                ,AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
    

hasAnyAuthority :多个权限

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin() // 自定义登录页面
            .loginPage("/login.html") //登录页面设置
            .loginProcessingUrl("/user/login") // 登录访问路径
            .defaultSuccessUrl("/login/index").permitAll() // 登录成功之后跳转路径
            .and().authorizeRequests()
            .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
            //.antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 -----------
            .antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个权限 -----------
            .anyRequest().authenticated()
            .and().csrf().disable();// 关闭csrf防护

}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    QueryWrapper<com.zhj.springsecuritydemo.entity.User> wrapper = new QueryWrapper<>();
    wrapper.eq("name",username);
    com.zhj.springsecuritydemo.entity.User user = userMapper.selectOne(wrapper);
    if(user == null){
        throw new UsernameNotFoundException("用户名不存在");
    }
    return new User(user.getName()
            ,new BCryptPasswordEncoder().encode(user.getPwd())
            ,AuthorityUtils.commaSeparatedStringToAuthorityList("manage"));
}

hasRole : 针对某一个角色

.antMatchers("/login/index").hasRole("sale") // 只有一个角色
return new User(user.getName()
        ,new BCryptPasswordEncoder().encode(user.getPwd())
        ,AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale")); // 注意前缀

hasAnyRole : 针对多个角色,多个角色满足其中一个角色即可

类似上面

自定义无权限访问页面

configure(HttpSecurity http)方法中添加:

// 自定以没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");

html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1> 哈哈 您没有权限</h1>

</body>
</html>

注解的使用

@Secured

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀 “_ROLE”;

使用注解先要开启注解功能 启动类配置:

@EnableGlobalMethodSecurity(securedEnabled=true)

然后对应controller方法 使用注解 例如:

@Secured("ROLE_sale")
@GetMapping("update")
public String update(){
    return "hello  update";
}
@PreAuthorize

注解适合进入方法的权限验证, @Preauthorize可以将登录用户的role/permission参数传到方法中

进入方法之前做权限验证

使用注解先要开启注解功能 启动类配置:

@EnableGlobalMethodSecurity(prePostEnabled = true)

然后对应controller方法 使用注解 例如:

@PreAuthorize("hasAuthority('admin')")
@GetMapping("insert")
public String insert(){
    return "hello  insert";
}
@PostAuthorize

注解使用不多,在方法执行后进行权限验证,适合验证带有返回值的权限

首先开启注解

然后对应controller方法 使用注解 如:

@PostAuthorize("hasAuthority('admin')")
@GetMapping("delete")
public String delete(){
    System.out.println("hello world");
    return "hello  delete";
}

用户点击退出操作

在配置类中配置:

http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll();

测试:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll();
    // 自定以没有权限访问的页面
    http.exceptionHandling().accessDeniedPage("/unauth.html");
    http.formLogin() // 自定义登录页面
            .loginPage("/login.html") //登录页面设置
            .loginProcessingUrl("/user/login") // 登录访问路径
            .defaultSuccessUrl("/success.html").permitAll() // 登录成功之后跳转路径
            .and().authorizeRequests()
            .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
            .antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 -----------
            //.antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个主体权限 -----------
            .antMatchers("/login/index").hasRole("sale") // 只有一个角色
            .anyRequest().authenticated()
            .and().csrf().disable();// 关闭csrf防护

}
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
login Success
<br>
<a href="/logout">退出</a>
</body>
</html>

自动登录

  1. cookie
  2. security框架提供的技术

请添加图片描述

security实现

  1. 配置类(securityConfig), 注入数据源 配置数据库连接对象

    @Autowired
    private DataSource datasource;
    
    // 配置数据库Bean对象
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(datasource);
        jdbcTokenRepository.setCreateTableOnStartup(true); // 自动建表
        return jdbcTokenRepository;
    }
    
  2. configure(HttpSecurity http) 配置记住我 ,有效时长 ,userDetailService

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll();
        // 自定以没有权限访问的页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin() // 自定义登录页面
                .loginPage("/login.html") //登录页面设置
                .loginProcessingUrl("/user/login") // 登录访问路径
                .defaultSuccessUrl("/success.html").permitAll() // 登录成功之后跳转路径
                .and().authorizeRequests()
                .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
                .antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 -----------
                //.antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个主体权限 -----------
                .antMatchers("/login/index").hasRole("sale") // 只有一个角色
                .anyRequest().authenticated()
    
    
          
          
                .and().rememberMe().tokenRepository(persistentTokenRepository()) //记住我
                .tokenValiditySeconds(60) // 设置时长
                .userDetailsService(userDetailsService) // 操作数据库
    
                
          
          
                .and().csrf().disable();// 关闭csrf防护
    
    }
    
  3. html

    <pre>
        <form action="/user/login" method="post">
            用户名: <input type="text" name="username"> <!-- name="username" 不允许变-->
            密码: <input type="password" name="password">  <!-- name="password" 不允许变-->
            <input type="checkbox" name="remember-me"> 记住我  <!-- name="remember-me" 不允许变-->
            <input type="submit" value="登录" >
        </form>
    
    </pre>
    
  4. 愉快的测试吧

CSRF

跨站请求伪造(英文:Cross-site request forgery),也被称为one-click attack 或者 session riding,通常缩写为CSRF或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法,跟跨网站脚本(XSS相比),XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。

网站请求攻击,简单的说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如 发邮件,发消息,甚至转账或购买行为),由于浏览器曾经认证过,所以被访问的网站会认为是正真的用户操作而去运行,这里用了web中用户身份验证的一个漏洞: 简单的身份验证只能保证请求发自某个用户的浏览器,去不能保证请求本身是用户自哪发的。

​ 从Spring 4.0 开始,默认情况下会启动CSRF保护,以防止攻击应用程序,Spring Security CSRF会针对PATCH、POST、PUT和DELETE方法进行保护

如何配置?

添加依赖

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

默认是开启的 注释disable

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll();
    // 自定以没有权限访问的页面
    http.exceptionHandling().accessDeniedPage("/unauth.html");
    http.formLogin() // 自定义登录页面
            .loginPage("/login.html") //登录页面设置
            .loginProcessingUrl("/user/login") // 登录访问路径
            .defaultSuccessUrl("/success.html").permitAll() // 登录成功之后跳转路径
            .and().authorizeRequests()
            .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
            .antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 -----------
            //.antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个主体权限 -----------
            .antMatchers("/login/index").hasRole("sale") // 只有一个角色
            .anyRequest().authenticated()


            .and().rememberMe().tokenRepository(persistentTokenRepository()) //记住我
            .tokenValiditySeconds(60) // 设置时长
            .userDetailsService(userDetailsService) // 操作数据库


            //.and().csrf().disable();

}

表单中配置

<form action="/user/login" method="post"> <!--this-->
  <!--加上隐藏项-->
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
    用户名: <input type="text" name="username"> <!-- name="username" 不允许变-->
    密码: <input type="password" name="password">  <!-- name="password" 不允许变-->
    <input type="checkbox" name="remember-me"> 记住我  <!-- name="remember-me" 不允许变-->
    <input type="submit" value="登录" >
</form>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值