Spring Boot中使用使用Spring Security和JWT
目标
1.Token鉴权
2.Restful API
3.Spring Security+JWT
开始
自行新建Spring Boot工程
引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
User类
非常简单的用户模型,将权限集成到了用户类中。
pacage com.domain
/**
* 用户模型
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
public class User { private String id;
private String username;
private String password;
private List<String> roles; ......
省略get、set方法
...... }
IUserRepository类
需实现对用户表的增删改查,此处可采用任意数据库,具体实现自行编写。
package com.dao
/**
* 用户表操作接口
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@Component
public interface IUserRepository{ /**
* 通过用户名查找用户
*
* @param username 用户名
* @return 用户信息
*/
User findByUsername(String username); }
JwtUser类
安全模块的用户模型
package com.security; import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /**
* 安全用户模型
*
* @author hackyo
* Created on 2017/12/8 9:20.
*/
public class JwtUser implements UserDetails { private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities; JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
} @Override
public String getUsername() {
return username;
} @JsonIgnore
@Override
public String getPassword() {
return password;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
} @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;
} }
JwtTokenUtil类
Token工具类
这里设置了密钥为aaaaaaaa,有效期为2592000秒
package com.security; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; /**
* JWT工具类
*
* @author hackyo
* Created on 2017/12/8 9:20.
*/
@Component
public class JwtTokenUtil implements Serializable { /**
* 密钥
*/
private final String secret = "aaaaaaaa"; /**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + 2592000L * 1000);
return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
} /**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
} /**
* 生成令牌
*
* @param userDetails 用户
* @return 令牌
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(2);
claims.put("sub", userDetails.getUsername());
claims.put("created", new Date());
return generateToken(claims);
} /**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
} /**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
return false;
}
} /**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
} /**
* 验证令牌
*
* @param token 令牌
* @param userDetails 用户
* @return 是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
String username = getUsernameFromToken(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
} }
JwtUserDetailsServiceImpl类
用户验证方法类
package com.security; import com.safepass.dao.IUserRepository;
import com.safepass.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import java.util.stream.Collectors; /**
* 用户验证方法
*
* @author hackyo
* Created on 2017/12/8 9:18.
*/
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService { private IUserRepository userRepository; @Autowired
public JwtUserDetailsServiceImpl(IUserRepository userRepository) {
this.userRepository = userRepository;
} @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
} else {
return new JwtUser(user.getUsername(), user.getPassword(), user.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
}
} }
JwtAuthenticationTokenFilter类
Token过滤器实现
package com.security; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* Token过滤器
*
* @author hackyo
* Created on 2017/12/8 9:28.
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil; @Autowired
public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
} @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String tokenHead = "Bearer ";
if (authHeader != null && authHeader.startsWith(tokenHead)) {
String authToken = authHeader.substring(tokenHead.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);
} }
EntryPointUnauthorizedHandler类
自定义了身份验证失败的返回值
package com.security; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* 自定401返回值
*
* @author hackyo
* Created on 2017/12/9 20:10.
*/
@Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint { @Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(401);
} }
RestAccessDeniedHandler类
自定了权限不足的返回值
package com.security; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* 自定403返回值
*
* @author hackyo
* Created on 2017/12/9 20:10.
*/
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler { @Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(403);
} }
WebSecurityConfig类
安全配置类
这里设置了禁止访问所有地址,除了用于验证身份的/user/**地址
同时密码的加密方式为BCrypt
package com.security; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /**
* 安全模块配置
*
* @author hackyo
* Created on 2017/12/8 9:15.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private UserDetailsService userDetailsService;
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
private RestAccessDeniedHandler restAccessDeniedHandler;
private PasswordEncoder passwordEncoder; @Autowired
public WebSecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter, EntryPointUnauthorizedHandler entryPointUnauthorizedHandler, RestAccessDeniedHandler restAccessDeniedHandler) {
this.userDetailsService = userDetailsService;
this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
this.entryPointUnauthorizedHandler = entryPointUnauthorizedHandler;
this.restAccessDeniedHandler = restAccessDeniedHandler;
this.passwordEncoder = new BCryptPasswordEncoder();
} @Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder);
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/user/**").permitAll()
.anyRequest().authenticated()
.and().headers().cacheControl();
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler); } }
IUserService类
定义用户的基本操作
package com.service; import com.domain.User; /**
* 用户操作接口
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
public interface IUserService { /**
* 用户登录
*
* @param username 用户名
* @param password 密码
* @return 操作结果
*/
String login(String username, String password); /**
* 用户注册
*
* @param user 用户信息
* @return 操作结果
*/
String register(User user); /**
* 刷新密钥
*
* @param oldToken 原密钥
* @return 新密钥
*/
String refreshToken(String oldToken); }
UserServiceImpl类
IUserService的实现类,注册时会将用户权限设置为ROLE_USER,同时将密码使用BCrypt加密
package com.service.impl; import com.dao.IUserRepository;
import com.domain.User;
import com.security.JwtTokenUtil;
import com.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; import java.util.ArrayList;
import java.util.List; /**
* 用户操作接口实现
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@Service
public class UserServiceImpl implements IUserService { private AuthenticationManager authenticationManager;
private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil;
private IUserRepository userRepository; @Autowired
public UserServiceImpl(AuthenticationManager authenticationManager, UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, IUserRepository userRepository) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.userRepository = userRepository;
} @Override
public String login(String username, String password) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return jwtTokenUtil.generateToken(userDetails);
} @Override
public String register(User user) {
String username = user.getUsername();
if (userRepository.findByUsername(username) != null) {
return "用户已存在";
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = user.getPassword();
user.setPassword(encoder.encode(rawPassword));
List<String> roles = new ArrayList<>();
roles.add("ROLE_USER");
user.setRoles(roles);
userRepository.insert(user);
return "success";
} @Override
public String refreshToken(String oldToken) {
String token = oldToken.substring("Bearer ".length());
if (!jwtTokenUtil.isTokenExpired(token)) {
return jwtTokenUtil.refreshToken(token);
}
return "error";
} }
UserController类
控制器,控制访问
package com.controller; import com.domain.User;
import com.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.*; /**
* 用户管理Controller
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@CrossOrigin
@RestController
@RequestMapping(value = "/user", produces = "text/html;charset=UTF-8")
public class UserController { private IUserService userService; @Autowired
public UserController(IUserService userService) {
this.userService = userService;
} /**
* 用户登录
*
* @param username 用户名
* @param password 密码
* @return 操作结果
* @throws AuthenticationException 错误信息
*/
@PostMapping(value = "/login", params = {"username", "password"})
public String getToken(String username, String password) throws AuthenticationException {
return userService.login(username, password);
} /**
* 用户注册
*
* @param user 用户信息
* @return 操作结果
* @throws AuthenticationException 错误信息
*/
@PostMapping(value = "/register")
public String register(User user) throws AuthenticationException {
return userService.register(user);
} /**
* 刷新密钥
*
* @param authorization 原密钥
* @return 新密钥
* @throws AuthenticationException 错误信息
*/
@GetMapping(value = "/refreshToken")
public String refreshToken(@RequestHeader String authorization) throws AuthenticationException {
return userService.refreshToken(authorization);
} }
使用
只需要在方法或类上加注解即可实现账号控制
例如,我们想控制该方法只允许用户本人使用,#号表示方法的参数,可以在参数中加上@P('name')来指定名称,同时也可直接使用模型,如user.username等
总之,其中可以写入任何Spring EL
@PreAuthorize("#username == authentication.name")
@GetMapping(value = "/getInfo")
public String getInfo(String username) {
return JSON.toJSONString(userService.getInfo(username));
}
另外也可以自定义控制注解,使用@PostFilter注解,并实现hasPermission类即可,同时需要在WebSecurityConfigurerAdapter中开启。
测试
运行程序后,我们使用Postman进行测试
1.注册
URL:http://localhost:8080/user/register
参数:username、password
返回success即为成功

2.登录
URL:http://localhost:8080/user/login
参数:username、password
可以看到服务器将我们的Token返回了

3.刷新Token
URL(GET方法):http://localhost:8080/user/refreshToken
参数:在Header中加入登录时返回的Token,注意,需要在Token前加上“Bearer ”,最后有个空格
Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1MTMzMTE1NjMsInN1YiI6IjEyMyIsImNyZWF0ZWQiOjE1MTI3MDY3NjM3NjB9.baiY8QcbJgq4FQMC2piN1smbW57WjDDTiRVIL9hJeC_DcPgcyJweWqkS6g7825mPKFlByuUx7XN8nUOIszDVcw
可以看到服务器给我们返回了新的Token,如果我们不加上Token的话,将无法访问

参考:
http://www.jianshu.com/p/6307c89fe3fa
http://www.jianshu.com/p/4468a2fff879
Spring Boot中使用使用Spring Security和JWT的更多相关文章
- Spring Boot中使用Spring Security进行安全控制
我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache ...
- Spring Boot中的静态资源文件
Spring Boot中的静态资源文件 1.SSM中的配置 2.Spring Boot 中的配置 2.1 整体规划 2.2 源码解读 2.3 自定义配置 2.3.1 application.prope ...
- Spring Boot中使用 Spring Security 构建权限系统
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,为应用系统提供声明式的安全 ...
- Spring Boot中集成Spring Security 专题
check to see if spring security is applied that the appropriate resources are permitted: @Configurat ...
- Spring Boot 中使用 Spring Security, OAuth2 跨域问题 (自己挖的坑)
使用 Spring Boot 开发 API 使用 Spring Security + OAuth2 + JWT 鉴权,已经在 Controller 配置允许跨域: @RestController @C ...
- Spring Boot中Starter是什么
比如我们要在Spring Boot中引入Web MVC的支持时,我们通常会引入这个模块spring-boot-starter-web,而这个模块如果解压包出来会发现里面什么都没有,只定义了一些POM依 ...
- 在spring boot中使用webSocket组件(二)
该篇演示如何使用websocket创建一对一的聊天室,废话不多说,我们马上开始! 一.首先先创建前端页面,代码如下图所示: 1.login.html <!DOCTYPE html> < ...
- Spring Boot 中的同一个 Bug,竟然把我坑了两次!
真是郁闷,不过这事又一次提醒我解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞.刨根问底,一步到位,再遇到类似问题就可以分分钟解决了. 如果大家没看过松哥之前 ...
- spring boot rest 接口集成 spring security(2) - JWT配置
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
随机推荐
- U盘启动盘还原
cmd运行 diskpart list disk clean 一般都是disk 1,不过最好先list查一下 右击桌面上的计算机图标,选择管理,进入磁盘管理,能看到u盘分区是未分配的(黑色),右击,新 ...
- HAOI2018 简要题解
这套题是 dy, wearry 出的.学长好强啊,可惜都 \(wc\) 退役了.. 话说 wearry 真的是一个计数神仙..就没看到他计不出来的题...每次考他模拟赛总有一两道毒瘤计数TAT 上午的 ...
- 【原创】hdu 1166 敌兵布阵(线段树→单点更新,区间查询)
学习线段树的第三天...真的是没学点啥好的,又是一道水题,纯模板,我个人觉得我的线段树模板还是不错的(毕竟我第一天相当于啥都没学...找了一整天模板,对比了好几个,终于找到了自己喜欢的类型),中文题目 ...
- github上的面试库
https://yuchengkai.cn/docs/zh/frontend/#%E5%86%85%E7%BD%AE%E7%B1%BB%E5%9E%8B https://github.com/taiz ...
- HDU5909Tree Cutting
题目大意 给定一颗树,每个点有点权,问对于每个m,有多少个联通块的权值异或和为m. 题解 解法1:可以考虑树形dp,设dp[u][i]表示以u为根的子树中u必须选,联通块权值异或值为i的联通块个数. ...
- centos7使用yum安装zabbix3.4
应用环境: 关于ZABBIX: zabbix一开强大的开源监控工具,同类型还有nagios,ganglia,cacti等监控工具,通过过去一年Google趋势就知道zabbix全球的相对热度: ...
- CSharp for Jupyter Notebook
之前说有机会就说下Linux下如何搭建C#版的交互编程,今天写篇文章还债^_^ Win下比较简单,可以自己看官方文档,下面逆天带大家搭建下Linux下的环境(官方方法有问题) 在线预览:https:/ ...
- css 圆形头像
方法一:背景图片(推荐) 好处是,图片长宽不等的情况下图片不会变形 .ui-photo { width: 100px; height: 100px; background: url("img ...
- 洛谷P4390 Mokia CDQ分治
喜闻乐见的CDQ分治被我搞的又WA又T..... 大致思路是这样的:把询问用二维前缀和的思想拆成4个子询问.然后施CDQ大法即可. 我却灵光一闪:树状数组是可以求区间和的,那么我们只拆成两个子询问不就 ...
- CF1101
D:题意:树上每个顶点有个权值,求最长链,满足链上gcd > 1 解:对每个质数建虚树,每个点只会拆成log个点,所以是log2的. #include <bits/stdc++.h> ...