java jwt实战,Spring Boot Security Jwt Authentication详解实战

引言

在这篇文章中,我们将通过JWT(JSOn Web Token)认证来保护我们的REST API 。我们将使用基于spring boot maven的配置来开发并保护我们的API,并提供单独的API用于注册并生成令牌。我们将扩展OncePerRequestFilter类,以使用JWT定义我们的自定义认证机制。认证机制可以应用于URL和方法。最后,我们将使用谷歌高级REST客户端测试实现。

项目结构

以下是我们将为spring  boot JWT认证而建立的最终项目结构。

3b70caa5b9bb4c6288ee726ab91ce0b7.png

JWT认证机制

以下类继承了OncePerRequestFilter,确保每个请求调度都有一次执行。该类检查授权头并验证JWT令牌并在上下文中设置验证。这样做可以保护我们的API免受那些没有任何授权令牌的请求。有关哪些资源需要保护以及哪些不可以配置WebSecurityConfig.java

JwtAuthenticationFilter.java

public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Autowired

private UserDetailsService userDetailsService;

@Autowired

private JwtTokenUtil jwtTokenUtil;

@Override

protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {

String header = req.getHeader(HEADER_STRING);

String username = null;

String authToken = null;

if (header != null && header.startsWith(TOKEN_PREFIX)) {

authToken = header.replace(TOKEN_PREFIX,"");

try {

username = jwtTokenUtil.getUsernameFromToken(authToken);

} catch (IllegalArgumentException e) {

logger.error("an error occured during getting username from token", e);

} catch (ExpiredJwtException e) {

logger.warn("the token is expired and not valid anymore", e);

} catch(SignatureException e){

logger.error("Authentication Failed. Username or Password not valid.");

}

} else {

logger.warn("couldn't find bearer string, will ignore the header");

}

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

UserDetails userDetails = userDetailsService.loadUserByUsername(username);

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

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));

authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));

logger.info("authenticated user " + username + ", setting security context");

SecurityContextHolder.getContext().setAuthentication(authentication);

}

}

chain.doFilter(req, res);

}

}

以下是用于生成身份验证令牌以及从令牌中提取用户名的util类。这里是我们需要url的配置,例如/ token / *和/ signup / *是公开可用的,其余url是受限于公共访问。

JwtTokenUtil.java

@Component

public class JwtTokenUtil implements Serializable {

public String getUsernameFromToken(String token) {

return getClaimFromToken(token, Claims::getSubject);

}

public Date getExpirationDateFromToken(String token) {

return getClaimFromToken(token, Claims::getExpiration);

}

public T getClaimFromToken(String token, Function claimsResolver) {

final Claims claims = getAllClaimsFromToken(token);

return claimsResolver.apply(claims);

}

private Claims getAllClaimsFromToken(String token) {

return Jwts.parser()

.setSigningKey(SIGNING_KEY)

.parseClaimsJws(token)

.getBody();

}

private Boolean isTokenExpired(String token) {

final Date expiration = getExpirationDateFromToken(token);

return expiration.before(new Date());

}

public String generateToken(User user) {

return doGenerateToken(user.getUsername());

}

private String doGenerateToken(String subject) {

Claims claims = Jwts.claims().setSubject(subject);

claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));

return Jwts.builder()

.setClaims(claims)

.setIssuer("http://devglan.com")

.setIssuedAt(new Date(System.currentTimeMillis()))

.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))

.signWith(SignatureAlgorithm.HS256, SIGNING_KEY)

.compact();

}

public Boolean validateToken(String token, UserDetails userDetails) {

final String username = getUsernameFromToken(token);

return (

username.equals(userDetails.getUsername())

&& !isTokenExpired(token));

}

}

以下是我们在上面的实现中使用的常量。

Constants.java

public class Constants {

public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60;

public static final String SIGNING_KEY = "devglan123r";

public static final String TOKEN_PREFIX = "Bearer ";

public static final String HEADER_STRING = "Authorization";

}

Spring Boot安全配置

现在让我们定义我们通常的spring引导安全配置。我们注入了userDetailsS​​ervice以从数据库中获取用户凭据。

在这里,注释@EnableGlobalMethodSecurity启用了方法级别的安全性,您可以使用注释(例如@Secured)注释您的方法,以在方法级别提供基于角色的身份验证。

WebSecurityConfig.java

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Resource(name = "userService")

private UserDetailsService userDetailsService;

@Autowired

private JwtAuthenticationEntryPoint unauthorizedHandler;

@Override

@Bean

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

@Autowired

public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailsService)

.passwordEncoder(encoder());

}

@Bean

public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {

return new JwtAuthenticationFilter();

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http.cors().and().csrf().disable().

authorizeRequests()

.antMatchers("/token/*").permitAll()

.anyRequest().authenticated()

.and()

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

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

http

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

}

@Bean

public BCryptPasswordEncoder encoder(){

return new BCryptPasswordEncoder();

}

}

以下是暴露在代表用户创建令牌的控制器,如果您注意到WebSecurityConfig.java我们已将此url配置为不进行身份验证,以便用户可以使用有效凭据生成JWT令牌。

AuthenticationController.java

@RestController

@RequestMapping("/token")

public class AuthenticationController {

@Autowired

private AuthenticationManager authenticationManager;

@Autowired

private JwtTokenUtil jwtTokenUtil;

@Autowired

private UserService userService;

@RequestMapping(value = "/generate-token", method = RequestMethod.POST)

public ResponseEntity register(@RequestBody LoginUser loginUser) throws AuthenticationException {

final Authentication authentication = authenticationManager.authenticate(

new UsernamePasswordAuthenticationToken(

loginUser.getUsername(),

loginUser.getPassword()

)

);

SecurityContextHolder.getContext().setAuthentication(authentication);

final User user = userService.findOne(loginUser.getUsername());

final String token = jwtTokenUtil.generateToken(user);

return ResponseEntity.ok(new AuthToken(token));

}

}

我们有非常简单的REST Apis公开测试用途。以下是实现。

@RestController

public class UserController {

@Autowired

private UserService userService;

@RequestMapping(value="/user", method = RequestMethod.GET)

public List listUser(){

return userService.findAll();

}

@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)

public User getOne(@PathVariable(value = "id") Long id){

return userService.findById(id);

}

}

以下是我们的实体类。

User.java

@Entity

public class User {

@Id

@GeneratedValue(strategy= GenerationType.AUTO)

private long id;

@Column

private String username;

@Column

@JsonIgnore

private String password;

@Column

private long salary;

@Column

private int age;

}

默认脚本

以下是在应用程序启动时插入的插入语句。

INSERT INTO User (id, username, password, salary, age) VALUES (1, 'Alex123', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 3456, 33);

INSERT INTO User (id, username, password, salary, age) VALUES (2, 'Tom234', '$2a$04$PCIX2hYrve38M7eOcqAbCO9UqjYg7gfFNpKsinAxh99nms9e.8HwK', 7823, 23);

INSERT INTO User (id, username, password, salary, age) VALUES (3, 'Adam', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 4234, 45);

JWT用户登录

由于我们有默认脚本来预先填充数据库中的数据以供测试,但我们也可以为用户注册公开一个API。使用此API用户可以注册并使用相同的用户名和密码来生成令牌。为此,我们在控制器类中添加了以下方法。

@RequestMapping(value="/signup", method = RequestMethod.POST)

public User saveUser(@RequestBody UserDto user){

return userService.save(user);

}

一旦添加,我们需要删除此URL的限制以供公众访问。为此,请在我们的Spring Boot security config中添加以下行。

.antMatchers("/token/*", "/signup").permitAll()

现在为了创建用户,我们在UserServiceImpl.java保存数据库中的用户记录方面做了简单的实现。这里要注意的一点是使用bcrypt编码器的密码加密。我们已经自动装配了我们定义为bean的相同编码器WebSecurityConfig.java

@Autowired

private BCryptPasswordEncoder bcryptEncoder;

@Override

public User save(UserDto user) {

User newUser = new User();

newUser.setUsername(user.getUsername());

newUser.setPassword(bcryptEncoder.encode(user.getPassword()));

newUser.setAge(user.getAge());

newUser.setSalary(user.getSalary());

return userDao.save(newUser);

}

这将显示用户注册过程的以下URL以生成JWT令牌。

384.html

测试应用程序

我们将使用Advanced REST Client来测试spring boot jwt认证。

使用令牌生成AuthToken访问资源,而不使用令牌访问资源

384.html

384.html

384.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值