Spring Boot中使用 Spring Security 构建权限系统

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

权限控制是非常常见的功能,在各种后台管理里权限控制更是重中之重.在Spring Boot中使用 Spring Security 构建权限系统是非常轻松和简单的.下面我们就来快速入门 Spring Security .在开始前我们需要一对多关系的用户角色类,一个restful的controller.

参考项目代码地址

- 添加 Spring Security 依赖

首先我默认大家都已经了解 Spring Boot 了,在 Spring Boot 项目中添加依赖是非常简单的.把对应的
spring-boot-starter-*** 加到pom.xml 文件中就行了

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

- 配置 Spring Security

简单的使用 Spring Security 只要配置三个类就完成了,分别是:

  • UserDetails

这个接口中规定了用户的几个必须要有的方法

public interface UserDetails extends Serializable {

    //返回分配给用户的角色列表
    Collection<? extends GrantedAuthority> getAuthorities();
    
    //返回密码
    String getPassword();

    //返回帐号
    String getUsername();

    // 账户是否未过期
    boolean isAccountNonExpired();

    // 账户是否未锁定
    boolean isAccountNonLocked();

    // 密码是否未过期
    boolean isCredentialsNonExpired();

    // 账户是否激活
    boolean isEnabled();
}
  • UserDetailsService

这个接口只有一个方法 loadUserByUsername,是提供一种用 用户名 查询用户并返回的方法。

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
  • WebSecurityConfigurerAdapter

这个内容很多,就不贴代码了,大家可以自己去看.

我们创建三个类分别继承上述三个接口

  • 此 User 类不是我们的数据库里的用户类,是用来安全服务的.
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
public class User implements UserDetails {

    private final String id;
    //帐号,这里是我数据库里的字段
    private final String account;
    //密码
    private final String password;
    //角色集合
    private final Collection<? extends GrantedAuthority> authorities;

    User(String id, String account, String password, Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.account = account;
        this.password = password;
        this.authorities = authorities;
    }

    //返回分配给用户的角色列表
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @JsonIgnore
    public String getId() {
        return id;
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return password;
    }
    
    //虽然我数据库里的字段是 `account`  ,这里还是要写成 `getUsername()`,因为是继承的接口
    @Override
    public String getUsername() {
        return account;
    }
    // 账户是否未过期
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    // 账户是否未锁定
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    // 密码是否未过期
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    // 账户是否激活
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
}
  • 继承 UserDetailsService
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    // jpa
    @Autowired
    private UserRepository userRepository;

    /**
     * 提供一种从用户名可以查到用户并返回的方法
     * @param account 帐号
     * @return UserDetails
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
        // 这里是数据库里的用户类
        User user = userRepository.findByAccount(account);

        if (user == null) {
            throw new UsernameNotFoundException(String.format("没有该用户 '%s'.", account));
        } else {
            //这里返回上面继承了 UserDetails  接口的用户类,为了简单我们写个工厂类
            return UserFactory.create(user);
        }
    }
}
  • UserDetails 工厂类
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
final class UserFactory {

    private UserFactory() {
    }

    static User create(User user) {
        return new User(
                user.getId(),
                user.getAccount(),
                user.getPassword(),
            mapToGrantedAuthorities(user.getRoles().stream().map(Role::getName).collect(Collectors.toList()))
        );
    }
    
    //将与用户类一对多的角色类的名称集合转换为 GrantedAuthority 集合
    private static List<GrantedAuthority> mapToGrantedAuthorities(List<String> authorities) {
        return authorities.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
}
  • 重点, 继承 WebSecurityConfigurerAdapter 类
/**
 * Created by Yuicon on 2017/5/14.
 * https://segmentfault.com/u/yuicon
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // Spring会自动寻找实现接口的类注入,会找到我们的 UserDetailsServiceImpl  类
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                // 设置UserDetailsService
                .userDetailsService(this.userDetailsService)
                // 使用BCrypt进行密码的hash
                .passwordEncoder(passwordEncoder());
    }

    // 装载BCrypt密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //允许跨域
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*")
                        .allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS")
                        .allowCredentials(false).maxAge(3600);
            }
        };
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 取消csrf
                .csrf().disable()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                // 允许对于网站静态资源的无授权访问
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/webjars/**",
                        "/swagger-resources/**",
                        "/*/api-docs"
                ).permitAll()
                // 对于获取token的rest api要允许匿名访问
                .antMatchers("/auth/**").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        // 禁用缓存
        httpSecurity.headers().cacheControl();
    }
}

- 控制权限到 controller

使用 @PreAuthorize("hasRole('ADMIN')") 注解就可以了

/**
 * 在 @PreAuthorize 中我们可以利用内建的 SPEL 表达式:比如 'hasRole()' 来决定哪些用户有权访问。
 * 需注意的一点是 hasRole 表达式认为每个角色名字前都有一个前缀 'ROLE_'。所以这里的 'ADMIN' 其实在
 * 数据库中存储的是 'ROLE_ADMIN' 。这个 @PreAuthorize 可以修饰Controller也可修饰Controller中的方法。
 **/
@RestController
@RequestMapping("/users")
@PreAuthorize("hasRole('USER')") //有ROLE_USER权限的用户可以访问
public class UserController {

   @Autowired
    private UserRepository repository;

    @PreAuthorize("hasRole('ADMIN')")//有ROLE_ADMIN权限的用户可以访问
    @RequestMapping(method = RequestMethod.GET)
    public List<User> getUsers() {
        return repository.findAll();
    }
}

- 结语

Spring Boot中 Spring Security 的入门非常简单,很快我们就能有一个满足大部分需求的权限系统了.而配合 Spring Security 的好搭档就是 JWT 了,两者的集成文章网络上也很多,大家可以自行集成.因为篇幅原因有不少代码省略了,需要的可以参考参考项目代码


程序道路
记录一些东西
495 声望
23 粉丝
0 条评论
推荐阅读
HashMap的实现原理笔记
HashMap是Java中常用的Map接口的实现类,因为在日常工作中非常频繁的出现,所以在大部分的Java面试中都会问几个关于HashMap的问题。掌握HashMap的实现原理,已经是Java程序员的基础操作了。

Yuicon1阅读 1.9k

一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。知识点详解秒杀系统架构图秒杀流程图秒杀系统设计这篇文章一万多...

王中阳Go32阅读 2.4k评论 1

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬14阅读 1.7k

万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide8阅读 1.6k

封面图
花了半个小时基于 ChatGPT 搭建了一个微信机器人
相信大家最近被 ChatGPT 刷屏了,其实在差不多一个月前就火过一次,不会那会好像只在程序员的圈子里面火起来了,并没有被大众认知到,不知道最近是因为什么又火起来了,而且这次搞的人尽皆知。

Java极客技术12阅读 3.1k评论 3

封面图
数据结构与算法:二分查找
一、常见数据结构简单数据结构(必须理解和掌握)有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小)无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)复杂数据结构树、 堆图二...

白鲸鱼9阅读 5.2k

PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go11阅读 2.7k评论 4

封面图
495 声望
23 粉丝
宣传栏