首页 > 编程语言 > 详细

JWT和Spring Security保护REST API

时间:2019-05-26 22:05:21      阅读:114      评论:0      收藏:0      [点我收藏+]

通常情况下,把API直接暴露出去是风险很大的,

我们一般需要对API划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户对应的API

(一)JWT是什么,为什么要使用它?

互联网服务离不开用户认证。一般流程是下面这样。

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
(引自:阮一峰的网络日志  http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息

JWT的结构

JWT包含了使用.分隔的三部分:

  • Header 头部

  • Payload 负载

  • Signature 签名

JWT的工作流程

下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected中)

1.用户导航到登录页,输入用户名、密码,进行登录
2.服务器验证登录鉴权,如果用户合法,根据用户的信息和服务器的规则生成JWT Token
3.服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
4.用户得到token,存在localStorage、cookie或其它数据存储形式中。
5.以后用户请求/protected中的API时,在请求的header中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer
6.服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
7.用户取得结果

(二)SpringSecurity

Spring Security 是为基于Spring的应用程序提供声明式安全保护的安全性框架。

一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)

两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,

也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。

系统通过校验用户名和密码来完成认证过程。

用户授权指的是验证某个用户是否有权限执行某个操作。

在一个系统中,不同用户所具有的权限是不同的。

比如对一个文件来说,有的用户只能进行读取,

而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,

而每个角色则对应一系列的权限。

对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。

(三)如何利用Spring Security和JWT一起来完成API保护

1.导入依赖

技术分享图片

2.配置application.properties

 spring.jackson.serialization.indent_output=true  //JSON格式化
 logging.level.org.springframework.security=info    //打印security日志记录

3.新增 AuthorityName + Authority + 修改 Admins

 

/**
 * 角色枚举类
 */
public enum AuthorityName {
    ROLE_ADMIN,ROLE_USER
}

import java.io.Serializable;

public class Authority implements Serializable {
    private Integer id;
    private AuthorityName name;

    public Authority() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public AuthorityName getName() {
        return name;
    }

    public void setName(AuthorityName name) {
        this.name = name;
    }
}
import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class Admins implements Serializable {
    private Integer aid;
    private String  aname;
    private String pwd;
    private Integer aexist;
    private Integer state;
    private Integer doid;
    private String by1;
    private Date lastPasswordResetDate;

    public Date getLastPasswordResetDate() {
        return lastPasswordResetDate;
    }

    public void setLastPasswordResetDate(Date lastPasswordResetDate) {
        this.lastPasswordResetDate = lastPasswordResetDate;
    }

    private List<Authority> authorities;

    public List<Authority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<Authority> authorities) {
        this.authorities = authorities;
    }

    public String getBy1() {
        return by1;
    }

    public void setBy1(String by1) {
        this.by1 = by1;
    }

    public Integer getDoid() {
        return doid;
    }

    public void setDoid(Integer doid) {
        this.doid = doid;
    }

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    public Integer getAexist() {
        return aexist;
    }

    public void setAexist(Integer aexist) {
        this.aexist = aexist;
    }

    public Integer getAid() {
        return aid;
    }

    public void setAid(Integer aid) {
        this.aid = aid;
    }

    public String getAname() {
        return aname;
    }

    public void setAname(String aname) {
        this.aname = aname;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

4.创建安全服务用户

JwtUser + JwtUserFactory + JwtUserDetailsServiceImpl + JwtAuthenticationResponse 需要实现UserDetails接口,用户实体即为Spring Security所使用的用户

/**
 * 安全服务的用户
 *  需要实现UserDetails接口,用户实体即为Spring Security所使用的用户
 */
public class JwtUser implements UserDetails {

    private final   Integer id;
    private final  Integer state;
    private final String username;
    private final String password;
    private final String email;
    private final  Collection<? extends GrantedAuthority> authorities;
    private final boolean enabled;
    private final Date lastPasswordResetDate;


    public JwtUser(Integer id, Integer state, String username, String password, String email, Collection<? extends GrantedAuthority> authorities, boolean enabled, Date lastPasswordResetDate) {
        this.id = id;
        this.state = state;
        this.username = username;
        this.password = password;
        this.email = email;
        this.authorities = authorities;
        this.enabled = enabled;
        this.lastPasswordResetDate = lastPasswordResetDate;
    }

    public Integer getState() {
        return state;
    }

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

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

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



    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

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

    public String getEmail() {
        return email;
    }

    @JsonIgnore
    public Date getLastPasswordResetDate() {
        return lastPasswordResetDate;
    }
}
public final  class JwtUserFactory {

    private JwtUserFactory() {
    }

    public static JwtUser create(Admins user){
         return new JwtUser(
                 user.getAid(),
                 user.getState(),
                 user.getAname(),
                 user.getPwd(),
                 user.getEmail(),
                 mapToGrandAuthroties(user.getAuthorities()),
                 user.getAexist()==1?true:false,
                 user.getLastPasswordResetDate()
         );
    }

    private static List<GrantedAuthority> mapToGrandAuthroties(List<Authority> authorities) {
        return authorities.stream()
                .map(authority -> new SimpleGrantedAuthority(authority.getName().name()))
                .collect(Collectors.toList());


    }

}
@Service
public class JwtUserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private AdminsMapper adminsMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Admins admins = this.adminsMapper.findByUsername(username);
        if(admins==null){
            throw  new UsernameNotFoundException("No User found with UserName :"+username);
        }else{
          return JwtUserFactory.create(admins);
        }
    }
}
public class JwtAuthenticationResponse implements Serializable {
  private static final long serialVersionUID = 4784951536404964122L;
  private final String token;

  public JwtAuthenticationResponse(String token) {
    this.token = token;
  }

  public String getToken() {
    return this.token;
  }
}

配置 application.properties 支持 mybatis 映射文件 xml

mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

5.创建让Spring控制的安全配置类:WebSecurityConfig

/**
 * 安全配置类
 */
@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private JwtAuthenticationEntryPoint unauthorizedHandler;

  @Autowired
  private UserDetailsService userDetailsService;


  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

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

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

  @Bean
  public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
    return new JwtAuthenticationTokenFilter();
  }

  /**
   * token请求授权
   *
   * @param httpSecurity
   * @throws Exception
   */
  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            // we don‘t need CSRF because our token is invulnerable
            .csrf().disable()
            .cors().and() // 跨域

            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

            // don‘t create session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

            .authorizeRequests()
            //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

            // allow anonymous resource requests
            .antMatchers(
                    HttpMethod.GET,
                    "/",
                    "/*.html",
                    "/favicon.ico",
                    "/**/*.html",
                    "/**/*.css",
                    "/**/*.js"
            ).permitAll()

            // Un-secure 登录 验证码
            .antMatchers(
                    "/api/auth/**",
                    "/api/verifyCode/**",
                    "/api/global_json"
            ).permitAll()
            // secure other api
            .anyRequest().authenticated();

    // Custom JWT based security filter
    // 将token验证添加在密码验证前面
    httpSecurity
            .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

    // disable page caching
    httpSecurity
            .headers()
            .cacheControl();
  }
}

6.在 XxxController 加一个修饰符 @PreAuthorize("hasRole(‘ADMIN‘)") 表示这个资源只能被拥有 ADMIN 角色的用户访问

@RequestMapping(value = "/protectedadmin", method = RequestMethod.GET)
  @PreAuthorize("hasRole(‘ADMIN‘)")
  public ResponseEntity<?> getProtectedAdmin() {
    return ResponseEntity.ok("Greetings from admin protected method!");
  }

  @RequestMapping(value = "/protecteduser", method = RequestMethod.GET)
  @PreAuthorize("hasRole(‘USER‘)")
  public ResponseEntity<?> getProtectedUser() {
    return ResponseEntity.ok("Greetings from user protected method!");
  }

 

JWT和Spring Security保护REST API

原文:https://www.cnblogs.com/wutongshu-master/p/10927870.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!