zoukankan      html  css  js  c++  java
  • 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 了,两者的集成文章网络上也很多,大家可以自行集成.因为篇幅原因有不少代码省略了,需要的可以参考参考项目代码

  • 相关阅读:
    三年Android开发经验,挥泪整理字节跳动、微软中国凉经,你不看看吗?
    App怎么做才能永不崩溃
    做了八年的Android开发,谁不是一边崩溃,一边默默坚守!
    阿里员工年年绩效A,晒出收入后感叹:996虽然痛苦,发钱时候真香
    2021阅读书单
    不动产测绘概念
    Elasticsearch 集成
    Elasticsearch 环境
    Elasticsearch 优化
    Elasticsearch入门
  • 原文地址:https://www.cnblogs.com/yuicon/p/7353910.html
Copyright © 2011-2022 走看看