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

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

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

参考项目代码地址git

- 添加 Spring Security 依赖

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

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

- 配置 Spring Security

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

  • UserDetails

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

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

这个内容不少,就不贴代码了,你们能够本身去看.segmentfault

咱们建立三个类分别继承上述三个接口api

  • 此 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 了,二者的集成文章网络上也不少,你们能够自行集成.由于篇幅缘由有很多代码省略了,须要的能够参考参考项目代码

相关文章
相关标签/搜索