jwt认证机制优势和原理_微服务架构系列之–前后端分离 JWT认证机制

efaf4e869e1115aac2d6ec323ba07d73.png

写在前面关于 Spring Security

Web系统的认证和权限模块也算是一个系统的基础设施了,几乎任何的互联网服务都会涉及到这方面的要求。在Java EE领域,成熟的安全框架解决方案一般有 Apache Shiro、Spring Security等两种技术选型。Apache Shiro简单易用也算是一大优势,但其功能还是远不如 Spring Security强大。Spring Security可以为 Spring 应用提供声明式的安全访问控制,起通过提供一系列可以在 Spring应用上下文中可配置的Bean,并利用 Spring IoC和 AOP等功能特性来为应用系统提供声明式的安全访问控制功能,减少了诸多重复工作。

  • 关于JWT

JSON Web Token (JWT),是在网络应用间传递信息的一种基于 JSON的开放标准((RFC 7519),用于作为JSON对象在不同系统之间进行安全地信息传输。主要使用场景一般是用来在 身份提供者和服务提供者间传递被认证的用户身份信息。关于JWT的科普,可以看看阮一峰老师的《JSON Web Token 入门教程》。本文实验环境如下:

  • Spring Boot版本:2.0.6.RELEASE
  • IDE:IntelliJ IDEA 2018.2.4

设计用户和角色本文实验为了简化考虑,准备做如下设计:

  • 设计一个最简角色表 role,包括 角色ID和 角色名称
  • 设计一个最简用户表 user,包括 用户ID, 用户名, 密码
f9b6e2eb0f82fc9d7c14b64ed8f557ce.png
  • 再设计一个用户和角色一对多的关联表 user_roles 一个用户可以拥有多个角色
20478fedb05f0a676de91d22160dbdd1.png

  • 创建Spring Security和JWT加持的Web工程pom.xml 中引入 Spring Security和 JWT所必需的依赖

<dependency>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-securityartifactId>

dependency>

<dependency>

<groupId>io.jsonwebtokengroupId>

<artifactId>jjwtartifactId>

<version>0.9.0version>

dependency>

  • 项目配置文件中加入数据库和 JPA等需要的配置

server.port=9991

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://121.196.XXX.XXX:3306/spring_security_jwt?useUnicode=true&characterEncoding=utf-8

spring.datasource.username=root

spring.datasource.password=XXXXXX

logging.level.org.springframework.security=info

spring.jpa.hibernate.ddl-auto=update

spring.jpa.show-sql=true

spring.jackson.serialization.indent_output=true

  • 创建用户、角色实体

用户实体 User

/**

* @ www.codesheep.cn

* 20190312

*/

@Entity

public class User implements UserDetails {

@Id

@GeneratedValue

private Long id;

private String username;

private String password;

@ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)

private List roles;

...

// 下面为实现UserDetails而需要的重写方法!

@Override

public Collection extends GrantedAuthority> getAuthorities() {

List authorities = new ArrayList<>();

for (Role role : roles) {

authorities.add( new SimpleGrantedAuthority( role.getName() ) );

}

return authorities;

}

...

}

此处所创建的 User类继承了 Spring Security的 UserDetails接口,从而成为了一个符合 Security安全的用户,即通过继承 UserDetails,即可实现 Security中相关的安全功能。

角色实体 Role:

/**

* @ www.codesheep.cn

* 20190312

*/

@Entity

public class Role {

@Id

@GeneratedValue

private Long id;

private String name;

... // 省略 getter和 setter

}

  • 创建JWT工具类

主要用于对 JWT Token进行各项操作,比如生成Token、验证Token、刷新Token等

/**

* @ www.codesheep.cn

* 20190312

*/

@Component

public class JwtTokenUtil implements Serializable {

private static final long serialVersionUID = -5625635588908941275L;

private static final String CLAIM_KEY_USERNAME = "sub";

private static final String CLAIM_KEY_CREATED = "created";

public String generateToken(UserDetails userDetails) {

...

}

String generateToken(Map claims) {

...

}

public String refreshToken(String token) {

...

}

public Boolean validateToken(String token, UserDetails userDetails) {

...

}

... // 省略部分工具函数

}

  • 创建Token过滤器,用于每次外部对接口请求时的Token处理

/**

* @ www.codesheep.cn

* 20190312

*/

@Component

public class JwtTokenFilter extends OncePerRequestFilter {

@Autowired

private UserDetailsService userDetailsService;

@Autowired

private JwtTokenUtil jwtTokenUtil;

@Override

protected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

String authHeader = request.getHeader( Const.HEADER_STRING );

if (authHeader != null && authHeader.startsWith( Const.TOKEN_PREFIX )) {

final String authToken = authHeader.substring( Const.TOKEN_PREFIX.length() );

String username = jwtTokenUtil.getUsernameFromToken(authToken);

if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

if (jwtTokenUtil.validateToken(authToken, userDetails)) {

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(

userDetails, null, userDetails.getAuthorities());

authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(

request));

SecurityContextHolder.getContext().setAuthentication(authentication);

}

}

}

chain.doFilter(request, response);

}

}

  • Service业务编写

主要包括用户登录和注册两个主要的业务

public interface AuthService {

User register( User userToAdd );

String login( String username, String password );

}

/**

* @ www.codesheep.cn

* 20190312

*/

@Service

public class AuthServiceImpl implements AuthService {

@Autowired

private AuthenticationManager authenticationManager;

@Autowired

private UserDetailsService userDetailsService;

@Autowired

private JwtTokenUtil jwtTokenUtil;

@Autowired

private UserRepository userRepository;

// 登录

@Override

public String login( String username, String password ) {

UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );

final Authentication authentication = authenticationManager.authenticate(upToken);

SecurityContextHolder.getContext().setAuthentication(authentication);

final UserDetails userDetails = userDetailsService.loadUserByUsername( username );

final String token = jwtTokenUtil.generateToken(userDetails);

return token;

}

// 注册

@Override

public User register( User userToAdd ) {

final String username = userToAdd.getUsername();

if( userRepository.findByUsername(username)!=null ) {

return null;

}

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

final String rawPassword = userToAdd.getPassword();

userToAdd.setPassword( encoder.encode(rawPassword) );

return userRepository.save(userToAdd);

}

}

  • Spring Security配置类编写(非常重要)

这是一个高度综合的配置类,主要是通过重写WebSecurityConfigurerAdapter 的部分 configure配置,来实现用户自定义的部分。

/**

* @ www.codesheep.cn

* 20190312

*/

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled=true)

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired

private UserService userService;

@Bean

public JwtTokenFilter authenticationTokenFilterBean() throws Exception {

return new JwtTokenFilter();

}

@Bean

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

@Override

protected void configure( AuthenticationManagerBuilder auth ) throws Exception {

auth.userDetailsService( userService ).passwordEncoder( new BCryptPasswordEncoder() );

}

@Override

protected void configure( HttpSecurity httpSecurity ) throws Exception {

httpSecurity.csrf().disable()

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

.authorizeRequests()

.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // OPTIONS请求全部放行

.antMatchers(HttpMethod.POST, "/authentication/**").permitAll() //登录和注册的接口放行,其他接口全部接受验证

.antMatchers(HttpMethod.POST).authenticated()

.antMatchers(HttpMethod.PUT).authenticated()

.antMatchers(HttpMethod.DELETE).authenticated()

.antMatchers(HttpMethod.GET).authenticated();

// 使用前文自定义的 Token过滤器

httpSecurity

.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

httpSecurity.headers().cacheControl();

}

}

  • 编写测试 Controller

登录和注册的 Controller:

/**

* @ www.codesheep.cn

* 20190312

*/

@RestController

public class JwtAuthController {

@Autowired

private AuthService authService;

// 登录

@RequestMapping(value = "/authentication/login

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值