SpringBoot中的Security简单入门实现

1 目录

  1. 理解什么是权限

  2. 学习Spring Security框架的基本概练和用法

  3. 能够使用Spring Security写一个入门级的安全应用

2 怎么在SpringBoot应用Security

为了让我们的接口能够根据用户的权限进行一定限制,我们引入了Security,通过权限,我们能让其在登陆后一段时间,能自由访问权限内的接口,但是不能访问权限外的接口方法。

为此我们需要对之前的项目进行改造,具体内容在以下几个部分;

2.1 导入maven依赖

<!--        springboot启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
<!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
<!--        test测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        Web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
<!--       mybatis和mysql驱动-->
        <!--        mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.springboot.version}</version>
        </dependency>
        <!--        mysqsl-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
​
<!--        分页-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.3</version>
        </dependency>
​
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.3.1</version>
        </dependency>
​
<!--        导入JPA依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
<!--        Swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>net.minidev</groupId>
            <artifactId>json-smart</artifactId>
        </dependency>
<!--        redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
<!--      日志收集-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
            <scope>compile</scope>
        </dependency>
<!--        Security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2.2 创建一个index门户接口

当前接口用于测试我们的权限认证和授权

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class IndexController {
​
    //test 1 : anyone could access
    @RequestMapping("/index")
    public String getAllUsers() {
        log.info("访问 index..");
        return "访问 index....";
    }
​
    //test 2 : someone has the Authority:'cx:updates_user'  could access
    @RequestMapping("/users")
    @PreAuthorize("hasAuthority('cx:updates_user')")// 授权:有cx:updates_user权限才能做该操作 否则报错403
    public String update() {
        //获取上下文
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = securityContext.getAuthentication();
        //在页面返回当前登录用户的所有权限
        return authentication.toString();
    }
​
}
 

2.3 创建Security配置类WebSecurityConfigure

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
​
// 这个有了以下任何一个注解都可以不用这个注解
// @Configuration
// 这个表示启用Web安全的注解,如果你已经是是一个web 项目,不需要使用此注解,
// @EnableWebSecurity //Springboot的自动配置机制WebSecurityEnablerConfiguration已经引入了该注解
​
//开启这个来判断用户对某个控制层的方法是否具有访问权限(见ProductController的@PreAuthorize)
// 这个注解很重要,如果没有这个注解,那么Controller里的方法将不受约束,只要登录成功就能访问。
@EnableGlobalMethodSecurity(prePostEnabled = true) //至关重要的注解,缺失导致验证不起效
@EnableWebSecurity
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
​
    @Autowired
    private UserDetailsService userDetailsService;
​
    // 参数: HttpSecurity http
    //**http.authorizeRequests()**
    // 下添加了多个匹配器,每个匹配器用来控制不同的URL接受不同的用户访问。
    // 简单讲,http.authorizeRequests()就是在进行请求的权限配置。
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //第二步:我们用我们自己的数据库数据来完成权限验证
        http.authorizeRequests()
                .antMatchers("/index").permitAll()//放行
                .anyRequest().authenticated()
                .and()
                .formLogin()
    }
    
    //这里配置密码为 BCrypt 加密方式,这样创建用户时,会对密码进行加密。而不是明文存储。
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2.4 配置yml文件

spring中加入security(注意层级)

spring:
  security:
    user:
      name: jing
      password: 1234

完成到这里,我们已经能够对无权访问的接口进行限制了。

尝试访问接口

http://localhost:8080/users

接下来进一步完善我们的security

2.5 创建UserDetails

使用之前的User实体类 实现UserDetails接口并实现其方法

package com.wanxi.springboot1018.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

@Data
@Component
@ApiModel(value = "用户",description = "用于描述用户对象")
@JsonIgnoreProperties({"enabled","accountNonExpired", "accountNonLocked", "credentialsNonExpired", "authorities"}) //避免把userdetail接口的方法序列化
public class User extends Base implements UserDetails {

    @ApiModelProperty(value = "用户ID",example = "123")
    private int id=0;
    @ApiModelProperty(value = "用户密码",example = "abc")
    private String password="";
    @ApiModelProperty(value = "用户姓名",example = "jing")
    private String username="";
    @ApiModelProperty(value = "用户电话",example = "180****8963")
    private String tel="";
    @ApiModelProperty(value = "生产日期",example = "2000-08-13")
    private String birthday="";
    @ApiModelProperty(value = "性别",example = "男")
    private String sex="";
    @ApiModelProperty(value = "授权变量",example = "")
    private Set<? extends GrantedAuthority> authorities=  new HashSet<>();

    private Boolean A ;
    private Boolean B ;

    public Boolean getA() {
        return A;
    }

    public void setA(Boolean a) {
        A = a;
    }

    public Boolean getB() {
        return B;
    }

    public void setB(Boolean b) {
        B = b;
    }

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

    //账号是否过期  count has expired
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //账号是否上锁 count has locked
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    //令牌是否过期 报错:credentials have expired
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //是否启用  报错:User is disabled 由于没有status状态这个字段,默认启用。
    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", password='" + password + '\'' +
                ", name='" + username + '\'' +
                '}';
    }

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

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

}

2.6 创建GrantedAuthority

新建一个类:Permission 表示用户的一个权限

并实现GrantedAuthority接口

(注意,这个实体类的成员一定要和数据库的字段相对应)

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
​
import java.util.Date;
​
/**
 *@description: 授予的权限信息,要实现GrantedAuthority
 */
@Data
// 不使用@Builder时以下@AllArgsConstructor和@NoArgsConstructor都可以不要,使用了就要需要,不然mybatis构建对象时会出错。
//@Builder
//@AllArgsConstructor
//@NoArgsConstructor
public class Permission implements GrantedAuthority {
​
    private Integer id;
    private Integer pid;
    private String name;
    private String value;
    private String icon;
    private Integer type;
    private String uri;
    private Integer status;
    private Date createTime;
    private String sort;
​
    //获取权限
    @Override
    public String getAuthority() {
        // 这里返回的内容要和Controller里的@PreAuthorize("hasAuthority('wx:product:read')")匹配
        return this.value;
    }
}
​

2.7 创建UserDetailsService

不同与之前的UserService,这个接口实现类会调用userService的方法,并被Security调用

import com.wanxi.springboot1018.entity.Permission;
import com.wanxi.springboot1018.entity.User;
import com.wanxi.springboot1018.service.UserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
​
/**
 *@description: UserDetailsService的实现类,Security 安全框架会调用这个接口的loadUserByUsername。
 *               这个类是Security 框架定义的接口,不是我们自己业务定义的接口,
 *               要想Security 按照我们的逻辑起作用,我们需要实现它
 */
@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Resource
    UserService userService;
​
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //通过用户名  访问数据库拿到  当前用户对象
        User user= userService.getUserByName(username);
        //一定要设置为加密后的密码
        //user.setName(username);
        user.setName("jing");
        user.setPassword("$2a$10$1M8F40YGBvgZrp0/UYtGxOTTFjiWxdXik1x1b.qliRk2tnOyWBv2i");
        // 紧接着 调用getPermissionsByUserId 方法 获取当前用户的 权限(用户->角色->权限)
        List<Permission> permissionList= userService.getPermissionsByUserId(user.getId());
        // 创建 HashSet 取代 List
        HashSet<Permission> permissions = new HashSet<>(permissionList);
        // 存入user对象
        user.setAuthorities(permissions);
        // 返回对象,包含该用户的所有权限
        return user;
    }
}

2.8 修改Config配置类

 http.authorizeRequests()
                .antMatchers("/index").permitAll()//放行
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                // 这一步,告诉Security 框架,我们要用自己的UserDetailsService实现类
                // 来传递UserDetails对象给框架,框架会把这些信息生成Authorization对象使用
                .userDetailsService(userDetailsService);

在这里加上后面的userDetailsService。

@PreAuthorize("hasAuthority('cx:updates_user')")// 授权:有cx:updates_user权限才能做该操作 否则报错403

这里的注解已经加上了

所以直接访问测试即可

2.10 访问测试

登录

报错403:权限不够

证明了我们的权限能够正常生效。并且拦截正确

2.11 其他验证:

一、把@PreAuthorize("hasAuthority('wx:product:read')") 这个值改一改,改成没有的试试

如果有注解但是没有权限:

如果没有注解:能够拿到数据

二、 把Config 类的@EnableGlobalMethodSecurity注解去掉,看看权限是否生效

恢复上面的注解

我们吧@EnableGlobalMethodSecurity去掉后,

尝试直接跨过登录访问无权限限制的接口:

尝试直接跨过登录访问无权限限制的接口:回到了登录界面

登录后再进行无权访问的接口:

可以看到,只要能登陆进来,就能访问,权限就是摆设

3

3.1

4 概念图

这些图需要在看完代码构成后,才能真正去的立体化去理解流程。

4.1 权限六表概念图

4.2 Secutity 流程图

4.3 Secutity 构成

以上是10月31日对29日的Security学习进行日常总结。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值