SpringSecurity —— 2、web 权限方案

目录

1、设置登录系统的账号、密码

1.1、方法一:在配置文件 application.properties 中设置

1.2、方法二:通过配置类

1.3、方法三:自定义实现类设置

2、实现数据库认证来完成用户登录

2.1、引入依赖

2.2、创建数据表及其实体类

2.3、在 application.properties 配置数据库

2.4、整合 MyBatisPlus,创建 Mapper 接口

2.5、在 MyUserDetailsService 调用 mapper 的方法查询数据库进行认证

2.6、在启动类上添加 @MapperScan 注解

3、自定义用户登录界面

3.1、在配置类进行相关配置

3.2、创建相关页面并配置 controller 路径

3.3、测试

4、基于角色或权限进行访问控制

4.1、hasAuthority 方法

4.1.1、在配置类设置当前访问地址的权限

4.1.2、在 UserDetailService 给返回的 User 对象设置权限

4.1.3、测试结果

4.2、hasAnyAuthority 方法

4.2.1、在配置类设置当前访问地址的权限

4.2.2、在 UserDetailService 给返回的 User 对象设置权限

4.3、hasRole 方法

4.3.1、在配置类设置当前访问地址的权限

 4.3.2、在 UserDetailService 给返回的 User 对象设置权限

 4.4、hasAnyRole 方法

5、自定义 403 页面

6、用户注销

6.1、在配置类添加退出的配置

6.2、测试

6.2.1、修改配置类,认证成功后跳转到成功页面

6.2.2、在成功页面添加超链接,链接到退出路径

6.2.3、

7、自动登录

7.1、创建表

7.2、配置类注入数据源,配置操作数据库对象

7.3、配置类配置自动登录

7.4、在登录页面添加自动登录复选框

7.5、测试

8、CSRF

8.1、CSRF 理解

8.2、开启 CSRF 防护

8.2.1、配置类中不要关闭 CSRF 防护

8.2.2、在登陆页面表单添加隐藏域

8.3、实现原理


1、设置登录系统的账号、密码

1.1、方法一:在配置文件 application.properties 中设置

spring.security.user.name=zyj
spring.security.user.password=123456

1.2、方法二:通过配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        // 将密码加密
        String password = bCryptPasswordEncoder.encode("123456");
        auth.inMemoryAuthentication().withUser("zyj").password(password).roles("admin");
    }

    /**
     * 为密码加密配置PasswordEncoder接口
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

1.3、方法三:自定义实现类设置

① 编写实现类,返回 User 对象,User 对象有用户名和密码和操作权限

@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User("zyj", new BCryptPasswordEncoder().encode("123456"), authorities);
    }
}

② 创建配置类,设置使用那个 userDetailsService 实现类

@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

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

    /**
     * 为密码加密配置PasswordEncoder接口
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

2、实现数据库认证来完成用户登录

2.1、引入依赖

        <!--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>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>

2.2、创建数据表及其实体类

数据表:

CREATE DATABASE spring_security;

USE spring_security;

CREATE TABLE users(
	id INT(11) PRIMARY KEY AUTO_INCREMENT,
	username VARCHAR(20),
	`password` VARCHAR(100)
);

INSERT INTO users
VALUES
(1, 'lucy', '123'),
(2, 'mary', '456');

实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {

    private Integer id;
    private String username;
    private String password;

}

2.3、在 application.properties 配置数据库

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_security?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=zyj123

2.4、整合 MyBatisPlus,创建 Mapper 接口

@Repository
public interface UsersMapper extends BaseMapper<Users> {
}

2.5、在 MyUserDetailsService 调用 mapper 的方法查询数据库进行认证

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

    @Autowired
    private UsersMapper usersMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 调用userMapper方法,根据用户名查询数据库
        QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        Users users = usersMapper.selectOne(queryWrapper);

        // 判断是否有此用户
        if(users == null){
            // 没有该用户名,认证失败
            throw new UsernameNotFoundException("用户名不存在!");
        }

        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        // 返回用户名对应的密码
        return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), authorities);
    }

}

2.6、在启动类上添加 @MapperScan 注解

@SpringBootApplication
@MapperScan("com.zyj.securitydemo1.mapper")
public class Securitydemo1Application {

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

}

3、自定义用户登录界面

3.1、在配置类进行相关配置

在之前的基础上,重写 protected void configure(HttpSecurity http) throws Exception 方法

@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

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

    /**
     * 为密码加密配置PasswordEncoder接口
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

    }
}

3.2、创建相关页面并配置 controller 路径

login.html   内容就是一个登录的表单而已

注:用户名框和密码框的 name 属性需要设置为 username 和 password,否则会报错。

若想用自定义的 name 属性,可以使用下图方框的两行设置

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/test/index" method="post">
        用户名:<input type="text" name="username">
        <br>
        密码:<input type="password" name="password">
        <br>
        <input type="submit" value="login">
    </form>
</body>
</html>

TestController 配置路径

    @GetMapping("/index")
    public String index(){
        return "hello index";
    }

3.3、测试

配置完成后,首先访问 /test/hello,发现其不用进行认证

然后访问 /login.html 登录页面,也不需要进行认证,且输入正确的用户名和密码后,会跳转到 /test/index 页面

4、基于角色或权限进行访问控制

4.1、hasAuthority 方法

如果当前的主体具有指定的权限,则返回 true,否则返回 false (只能定义一个权限)

4.1.1、在配置类设置当前访问地址的权限

修改配置类 SecurityConfigTest

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()  // 自定义自己编写的登录页面
                .loginPage("/login.html")   // 设置登录页面
                .loginProcessingUrl("/user/login")       // 登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()   // 登录成功后跳转的路径,permitAll()允许所有操作
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello", "/user/login").permitAll()  // 设置无需认证的路径,可以直接访问
                // 设置访问地址有哪些权限,下面这个语句只有在当前登录用户具有admin权限才可以访问
                .antMatchers("/test/index").hasAuthority("admin")
                .anyRequest().authenticated()
                .and().csrf().disable();  // 关闭csrf防护
    }

4.1.2、在 UserDetailService 给返回的 User 对象设置权限

在 MyUserDetailService 修改 

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 调用userMapper方法,根据用户名查询数据库
        QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        Users users = usersMapper.selectOne(queryWrapper);

        // 判断是否有此用户
        if(users == null){
            // 没有该用户名,认证失败
            throw new UsernameNotFoundException("用户名不存在!");
        }

        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
        // 返回用户名对应的密码
        return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), authorities);
    }

4.1.3、测试结果

若 MyUserDetailService 中返回的 User 对象没有 admins 权限,认证后,页面显示如下,没有访问权限,403

 反之,若返回的 User 对象有 admins 权限,则可以正常显示页面

4.2、hasAnyAuthority 方法

如果当前的主体有任何提供的角色(不同的权限字符串之间用逗号分隔的字符串数组)的话,返回 true. 

4.2.1、在配置类设置当前访问地址的权限

修改配置类 SecurityConfigTest

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()  // 自定义自己编写的登录页面
                .loginPage("/login.html")   // 设置登录页面
                .loginProcessingUrl("/user/login")       // 登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()   // 登录成功后跳转的路径,permitAll()允许所有操作
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello", "/user/login").permitAll()  // 设置无需认证的路径,可以直接访问
                // 设置访问地址有哪些权限,下面这个语句只有在当前登录用户具有admins权限才可以访问,且权限只能定义一个
                //.antMatchers("/test/index").hasAuthority("admins")
                // 设置访问地址有哪些权限,下面这个语句只有在当前登录用户具有admins权限才可以访问,且权限可以定义多个
                .antMatchers("/test/index").hasAnyAuthority("admins", "manager")
                .anyRequest().authenticated()
                .and().csrf().disable();  // 关闭csrf防护
    }

4.2.2、在 UserDetailService 给返回的 User 对象设置权限

4.3、hasRole 方法

hasRole 方法:如果用户具备给定角色就允许访问,返回 true,否则出现 403。

4.3.1、在配置类设置当前访问地址的权限

 4.3.2、在 UserDetailService 给返回的 User 对象设置权限

 4.4、hasAnyRole 方法

hasAnyRole 方法:表示用户具备任何一个条件都可以访问。

配置类 SecurityConfigTest

 UserDetailService

5、自定义 403 页面

① 首先在 static 下创建一个 403 页面 unauth.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>无访问权限</h1>
</body>
</html>

② 在配置类进行配置即可

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()  // 自定义自己编写的登录页面
                .loginPage("/login.html")   // 设置登录页面
                .loginProcessingUrl("/user/login")       // 登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()   // 登录成功后跳转的路径,permitAll()允许所有操作
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello", "/user/login").permitAll()  // 设置无需认证的路径,可以直接访问
                // 设置访问地址有哪些权限,下面这个语句只有在当前登录用户具有admins权限才可以访问,且权限只能定义一个
                //.antMatchers("/test/index").hasAuthority("admins")
                // 设置访问地址有哪些权限,下面这个语句只有在当前登录用户具有admins权限才可以访问,且权限可以定义多个
                //.antMatchers("/test/index").hasAnyAuthority("admins", "manager")
                // 如果当前主体具有指定的角色,则返回true。
                //.antMatchers("/test/index").hasRole("sale")
                .antMatchers("/test/index").hasAnyRole("aaa", "bbb", "sale")
                .anyRequest().authenticated()
                .and().csrf().disable();  // 关闭csrf防护

        // 配置没有访问权限403跳转到的自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
    }

6、用户注销

6.1、在配置类添加退出的配置

SecurityConfigTest

6.2、测试

6.2.1、修改配置类,认证成功后跳转到成功页面

6.2.2、在成功页面添加超链接,链接到退出路径

success.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    认证成功
    <a href="/logout">退出</a>
</body>
</html>

6.2.3、

认证成功后,显示 success.html 页面的内容。

点击退出后,跳转到 /test/hello 页面。此时,若访问 /test/index 页面,会跳转到认证页面,需要认证后方可访问。

7、自动登录

7.1、创建表

CREATE TABLE `persistent_logins` (
	username VARCHAR(64) NOT NULL, 
	series VARCHAR(64) PRIMARY KEY, 
	token VARCHAR(64) NOT NULL, 
	# 在用户不给定值的情况下,使用当前时间为默认值
	last_used TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=INNODB DEFAULT CHARSET=utf8;

7.2、配置类注入数据源,配置操作数据库对象

SecurityConfigTest

    // 注入数据源
    @Autowired
    private DataSource dataSource;

    // 配置操作数据库对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //jdbcTokenRepository.setCreateTableOnStartup(true); // 在数据库自动创建表persistent_logins
        return jdbcTokenRepository;
    }

7.3、配置类配置自动登录

7.4、在登录页面添加自动登录复选框

自动登录复选框的 name 属性必须为 remember-me,若想设定自定义的,使用下图红框中的语句

login.html 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<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">
        <br>
        <input type="checkbox" name="remember-me">自动登录
        <br>
        <input type="submit" value="login">
    </form>
</body>
</html>

7.5、测试

勾选自动登录选项后认证,发送的请求中含有键为 remember-me 的 cookie

 此时关闭浏览器再重新打开,并访问相关的页面,可以发现无需认证

8、CSRF

8.1、CSRF 理解

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF 是一种挟制用户在当前已

登录的 Web 应用程序上执行非本意的操作的攻击方法。跟 跨网站脚本XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

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

Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用程序,Spring Security CSRF 针对 PATCHPOSTPUT DELETE 方法进行防护

8.2、开启 CSRF 防护

8.2.1、配置类中不要关闭 CSRF 防护

8.2.2、在登陆页面表单添加隐藏域

请求到来时,从请求中提取 csrfToken,和保存的 csrfToken 做比较,进而判断当前请求是否合法。主要通过 CsrfFilter 过滤器来完成。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/user/login" method="post">
        <!-- CSRF防护的隐藏域 -->
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
        用户名:<input type="text" name="username">
        <br>
        密码:<input type="password" name="password">
        <br>
        <input type="checkbox" name="remember-me">自动登录
        <br>
        <input type="submit" value="login">
    </form>
</body>
</html>

8.3、实现原理

① 生成 csrfToken 保存到 HttpSession 或者 Cookie 中。 

② 请求到来时,从请求中提取 csrfToken,和保存的 csrfToken 做比较,进而判断当前请求是否合法。主要通过 CsrfFilter 过滤器来完成。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值