0
点赞
收藏
分享

微信扫一扫

记录自己学习springSecurity基本使用

阿尚青子自由写作人 2022-02-12 阅读 79
安全

1.什么是SpringSecurity,能做什么?

它是一个安全框架,主要能帮助我们做认证(你是谁)和授权(你能做什么)两大功能。

2.Springboot集成SpringSecurity(体验版)

1.首先导入依赖

<!--pringboot依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

 2.编写一个控制器。写一个映射方法。前端访问这个URL。你会发现会被Security拦截,需要输入用户名和密码。用户名security默认是user。密码默认是程序控制台上打印的。(Using generated security password: xxxxxxxx)

@Controller
public class HelloSecurityController {
    /**
     * 第一次测试security。
     * @return
     */
    @GetMapping("/helloSecurity")
    @ResponseBody
    public String helloSecurity(){
        return "helloSecurity";
    }
}

3. 如果想要打破默认的security的用户名和密码(自定义登录与密码),只需在yaml配置如下:

spring:
  security:
    user:
      name: admin
      password: admin

3.基于内存版本的登录验证与角色权限管理

假设现在有一个需求是这样的:给你一个部门管理系统。

系统有两个角色:USER,ADMIN。

有两个功能:

        1)查看部门用户列表 (USER,ADMIN角色都可以使用)

        2)删除部门某用户(ADMIN角色才能使用)

1. 编写一个类继承WebSecurityConfigurerAdapter。重写configura方法。在方法中加入用户名和密码。并且在配置类上开启Security功能(@EnableWebSecurity)和

@EnableGlobalMethodSecurity(prePostEnabled = true)

注意:security5版本以上,需要密码加密。不然会出现一个passwordEncoder null 的异常.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("123456")).roles("ADMIN");
        auth.inMemoryAuthentication().withUser("zs").password(passwordEncoder.encode("123456")).roles("USER");
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

其次,在controller中加入这两个功能代码如下:

/**
     * 展示部门列表功能
     * @return
     */
    @GetMapping("/show/userList")
    @PreAuthorize(value = "hasAnyRole('USER','ADMIN')")
    @ResponseBody
    public String showUserList(){
        return "展示部门用户列表(角色要求:USER/ADMIN)";
    }

    /**
     * 删除某部门用户功能
     * @return
     */
    @GetMapping("/deleteUser")
    @PreAuthorize(value = "hasAnyRole('ADMIN')")
    @ResponseBody
    public String deleteUser(){
        return "删除某用户(角色要求:ADMIN)";
    }

2. 其次在用浏览器测试,你会发现zs,admin 都可以使用展示部门列表,但是删除功能只能是admin用户才可以。

注意:浏览器在测试的时候没换一个账户时,需要清除浏览器缓存在测试。 如果出现403错误,就表示被拒绝了,因为403错误页面是Springboot自带的。

3.最佳实战:基于数据库版本

前面展示了基于内存版本的,现在升级为基于数据库版本的.

现在想要实现的是:项目首先进入登录页面,登录部门管理系统。登录成功后,部门管理系统会根据不同的角色控制他们的权限功能。

补充:SpringSecurity框架中的UserDetailsService 和 User 类

UserDetails: 是一个接口,用来获取用户权限、密码、账户、判断账户是否锁定等操作。 

user:是一个SpringSecurity高度抽象的用户类,实现了UserDetails接口。

所以如果你想基于数据库版本的。就必须实现UserDetailsService接口,重写里面的

public UserDetails loadUserByUsername(String username); 根据username去数据库登录登录校验查询。然后构建一个UserDetails 返回给security框架。

准备工作:

 准备sys_user表,sys_role表,sys_user_role_relation表。

sys_user:

+-----------------------+--------------+------+-----+---------+----------------+
| Field                 | Type         | Null | Key | Default | Extra          |
+-----------------------+--------------+------+-----+---------+----------------+
| id                    | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| realName              | varchar(255) | YES  |     | NULL    |                |
| username              | varchar(255) | YES  |     | NULL    |                |
| password              | varchar(255) | YES  |     | NULL    |                |
| accountNonExpired     | tinyint(1)   | YES  |     | 1       |                |
| accountNonLocked      | tinyint(1)   | YES  |     | 1       |                |
| credentialsNonExpired | tinyint(1)   | YES  |     | 1       |                |
| enabled               | tinyint(1)   | YES  |     | 1       |                |
| createTime            | date         | YES  |     | NULL    |                |
+-----------------------+--------------+------+-----+---------+----------------+

sys_role:

+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| role     | varchar(255) | YES  |     | NULL    |                |
| describe | varchar(255) | YES  |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+

sys_user_role_relation:

+--------+------------+------+-----+---------+-------+
| Field  | Type       | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+
| userId | bigint(20) | NO   |     | NULL    |       |
| roleId | bigint(20) | NO   |     | NULL    |       |
+--------+------------+------+-----+---------+-------+

Dao层框架自定义,本次使用mybatisPlus。用mybatisplus自动生成对应的dao层代码。所以dao层代码全部省略了。

加入themeleaf依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

 

前端页面(随便写一下):

登录页面:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Spring Security Example </title>
</head>
<body>
    <!--显示失败信息-->
    <div th:if="${param.error}">
        Invalid username and password.
    </div>
    <div th:if="${param.logout}">
        You have been logged out.
    </div>
    <form th:action="@{/login.do}" method="post">
        <div><label> User Name : <input type="text" name="username"/> </label></div>
        <div><label> Password: <input type="password" name="password"/> </label></div>
        <div><input type="submit" value="Sign In"/></div>
    </form>
</body>
</html>

系统主页:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
    <a href="/show/userList">查看用户</a><br>
    <a href="/deleteUser">删除用户</a><br>
</body>
</html>

查看用户列表页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>展示用户</title>
</head>
<body>
<h2>展示所有用户(角色要求:ADMIN/USER)</h2>
</body>
</html>

删除用户页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>删除用户(角色要求:ADMIN)</h2>
</body>
</html>

后端Java:

  1. 自定义一个实体类MySecurityUser implement UserDetails。重写里面所有的方法。

  2. 编写一个UserDetailsService的实现类,重写loadUserByUsername方法。在方法中查询数据库构建MySecurityUser对象返回即可。

  3. 在WebSecurityConfigurerAdapter 这个类中改版不要基于内存的了,而是基于UserDetailsService的。

1.
@Data
public class MySecurityUser implements UserDetails {
    private Long id;
    private String username;
    private String realName;
    private String password;
    private Integer accountNonExpired;
    private Integer accountNonLocked;
    private Integer credentialsNonExpired;
    private Integer enabled;
    private Date createTime;
    List<GrantedAuthority> authorityList;     // 该用户的角色列表集合

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorityList;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired==1;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked==1;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired==1;
    }

    @Override
    public boolean isEnabled() {
        return enabled==1;
    }
}

2.

/**
 * 基于数据库版本的控制角色权限:
 *      重点是在loadUserByUsername根据username去数据库查询(相当于一个登录功能)。
 *      如果查询到了,就构建UserDetails 对象给security框架。
 */
@Component("MyUserDetailsImpl")
public class MyUserDetailsImpl implements UserDetailsService {
    @Autowired
    ISys_userService iSys_userService;
    @Autowired
    ISys_user_role_relationService user_role_relationService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MySecurityUser resUser = null;
        // 根据username查询
        Sys_user sys_user = iSys_userService.getOne(new QueryWrapper<Sys_user>().eq("username", username));
        if(sys_user!=null){
            // 构建一个UserDetails对象给框架。
            resUser = new MySecurityUser();
            BeanUtils.copyProperties(sys_user,resUser);
            // 查询该username用户的权限
            List<GrantedAuthority> authorities = user_role_relationService.getRoleByUserId(resUser.getId());
            resUser.setAuthorityList(authorities);
        }
        return resUser;
    }
}

// 我这里构建username用户权限的代码是如下。重点想表达的意思是:角色名字前面需要加入 ROLE_ 这是security框架规定的。
    @Override
    public List<GrantedAuthority> getRoleByUserId(Long userId) {
        List<Sys_role> list = user_role_relationMapper.getRoleByUserId(userId);
        List<GrantedAuthority> collect = list.stream().map((role) -> {
            GrantedAuthority g = new SimpleGrantedAuthority("ROLE_" + role.getRole());
            return g;
        }).collect(Collectors.toList());
        return collect;
    }

3.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("MyUserDetailsImpl")
    UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 改版基于userDetailsService 来实现数据库版本。
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 表示哪些请求需要被放行。(首页/登录页/静态资源等允许放行)
                .antMatchers("/", "/static/**").permitAll()
                //而其他的请求都需要认证
                .anyRequest()
                .authenticated()
                .and()
                // 设置自定义的登录页面url
                .formLogin()
                .loginPage("/index.html")
                //指定处理登录请求的路径,对应form表单的action地址
                .loginProcessingUrl("/login.do").permitAll()
                // 指定错误时跳转到的url
                //.failureUrl("/error")
                // 指定登录成功跳转的url路径
                .defaultSuccessUrl("/main.html",true);
    }
}

4. 假设现在数据库中的数据是这样的:

张三(zs),拥有USER角色。

管理员(admin),拥有ADMIN,USER两个角色。

项目测试会发现张三只能使用展示用户列表功能, 删除功能只能是拥有ADMIN角色的管理员才可以使用。

总结:

本次记录自己学习security框架的成果。

版本3其实就是对于基于内存的改本。 加入了dao层的逻辑。

主要就是在

MyUserDetailsImpl类中 自己根据username 查询数据库自己构建一个UserDetails类扔给security框架而已。

如果我自己自己实现这种权限控制,大致思路就是在访问controller的时候,先经过过滤器或者拦截器。然后在此处判断该用户是否有资格访问该映射方法。如果有资格,放行。否者拦截。

后续有时间可以看看security的原理

举报

相关推荐

0 条评论