在传统的开发中,登录采取的都是基于session认证的方式,session认证,session是由服务器产生的,服务器将产生的sessionId发送给客户端,客户端在将sessionId保存到cookie中。当请求时候客户端每次都需要携带这个sessionId,服务器将之前发送的sessionId比较客户端发送的sessionId,如果一致就完成认证。由于服务器要保存session数据,所以压力就很大,并且cookie不安全容易被跨站攻击。
token是无状态认证,服务端不需要存token的数据,用户每次认证成功都会产生一串token,每次请求任何资源的时候客户端必须携带这串token,不再需要用户名和密码,这样扩展性更高,现在也是非常的流行的认证方式。
什么是jwt?下面是来自于jwt官网的解释
JSON Web令牌(JWT)是一种开放标准(RFC
7519),它定义了一种紧凑和独立的方式,用于在各方之间以JSON对象的形式安全地传输信息。可以验证和信任此信息,因为它是数字签名的。JWT可以使用秘密签名(使用HMAC算法),也可以使用RSA或ECDSA对公钥/私钥对进行签名。
什么时候使用jwt?
这是使用JWT最常见的场景。一旦用户登录,每个后续请求都将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用的一种特性,因为它开销小,而且能够很容易地跨不同领域使用。信息交换:JSON
Web令牌是在各方之间安全地传输信息的一种好方法。因为JWT可以进行签名-例如,使用公钥/私钥对-所以您可以确保发件人是他们所称的发送者。此外,由于签名是使用报头和有效载荷计算的,您还可以验证内容没有被篡改。
jwt的认证流程
1.用户使用用户名密码请求服务器
2.服务器进行验证用户信息
3.服务器通过验证发送给用户一个token
4.客户端存储token,并在每次请求时附加这个token值
5.服务器验证token,并返回数据
jwt的构成
jwt由三部分构成header、payload、secred 构成
其中header、payload是被加密了
在前后端不分离的情况下用传统的session认证当然是没有什么的问题的,但是现在都提倡前后端分离,后端只给前端数据。那么就推荐使用token认证了,当然用session认证也是可以了,前后端分离中极力推荐使用token来认证授权。
如何在springboot 中优雅的使用jwt认证?非常的简单首先在您的项目中引如jwt和security框架的的相关依赖,如下图所示
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
引入之后就能使用jwt了。
1.在实体类中就是用户信息的表User 中要继承UserDetails接口实现这几个方法如下图所示
public class UserDetail implements UserDetails {
private long id;
private String username;
private String password;
private Role role;
private Date lastPasswordResetDate;
public UserDetail(long id, String username, Role role, String password) {
this.id = id;
this.username = username;
this.password = password;
this.role = role;
}
public UserDetail(String username, String password, Role role) {
this.username = username;
this.password = password;
this.role = role;
}
public UserDetail(long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
//返回分配给用户的角色列表
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(role.getName()));
return authorities;
}
public long getId() {
return id;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
/**
* 账户是否未过期
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 账户是否未锁定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 密码是否未过期
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/** 账户是否激活
*/
@Override
public boolean isEnabled() {
return true;
}
public Date getLastPasswordResetDate() {
return lastPasswordResetDate;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public void setId(long id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setLastPasswordResetDate(Date lastPasswordResetDate) {
this.lastPasswordResetDate = lastPasswordResetDate;
}
还有角色表 role
@Data
@Builder
public class Role {
private Long id;
private String name;
}
role表是用的权限表 其中name是权限名称
保存用户token的表这个表不是数据库是临时的如下图:
@Data
@AllArgsConstructor
public class ResponseUserToken {
private String token;
private UserDetail userDetail;
}
实现我们的Dao,不管你使用的是mybatis的mapper还是hibernate的repository都是一样的的操作。下面是UserService中的方法
/**
* 注册用户
* @param userDetail
* @return
*/
UserDetail register(UserDetail userDetail);
/**
* 登陆
* @param username
* @param password
* @return
*/
ResponseUserToken login(String username, String password);
/**
* 刷新Token
* @param oldToken
* @return
*/
ResponseUserToken refresh(String oldToken);
/**
* 登出
* @param token
*/
void logout(String token);
/**
* 根据Token获取用户信息
* @param token
* @return
*/
UserDetail getUserByToken(String token);
// 详细的UserServiceImpl实现如下图所示:
private AuthenticationManager authenticationManager;
private UserDetailsService userDetailsService;
private JwtUtils jwtTokenUtil;
@Autowired
private UserRepository userRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Value("${scitc.jwt.header}")
private String tokenHead;
@Autowired
public AuthServiceImpl(AuthenticationManager authenticationManager, @Qualifier("CustomUserDetailsService")
UserDetailsService userDetailsService, JwtUtils jwtTokenUtil, UserRepository authMapper) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.userRepository = userRepository;
}
@Transactional
@Override
public User register(User user) {
final String username = user.getUsername();
if(userRepository.findByUsername(username)!=null) {
throw new UsernameNotFoundException("用户已存在:" + username );
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode(user.getPassword()));
user.setLastPasswordResetDate(new Date());
user = userRepository.save(user);
Authority role = new Authority();
role.setId(user.getId());
role.setUserId(user.getId());
role.setName("ROLE_USER");
role = authorityRepository.save(role);
user.getRoles().add(role);
user = userRepository.save(user);
return user;
}
@Override
public ResponseUserToken login(String username, String password) {
//用户验证
final Authentication authentication = authenticate(username, password);
//存储认证信息
SecurityContextHolder.getContext().setAuthentication(authentication);
//生成token
final User userDetail = (User) authentication.getPrincipal();
final String token = jwtTokenUtil.generateAccessToken(userDetail);
//存储token
jwtTokenUtil.putToken(username, token);
return new ResponseUserToken(token, userDetail);
}
@Override
public void logout(String token) {
token = token.substring(tokenHead.length());
String userName = jwtTokenUtil.getUsernameFromToken(token);
jwtTokenUtil.deleteToken(userName);
}
@Override
public ResponseUserToken refresh(String oldToken) {
String token = oldToken.substring(tokenHead.length());
String username = jwtTokenUtil.getUsernameFromToken(token);
User userDetail = (User) userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.canTokenBeRefreshed(token, userDetail.getLastPasswordResetDate())){
token = jwtTokenUtil.refreshToken(token);
return new ResponseUserToken(token, userDetail);
}
return null;
}
@Override
public User getUserByToken(String token) {
token = token.substring(tokenHead.length());
System.out.println("token:" + token);
return jwtTokenUtil.getUserFromToken(token);
}
private Authentication authenticate(String username, String password) {
try {
//该方法会去调用userDetailsService.loadUserByUsername()去验证用户名和密码,如果正确,则存储该用户名密码到“security 的 context中”
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException | BadCredentialsException e) {
throw new BaseException("出错");
}
}
}
其中的AuthenticationManager 代表的认证信息管理,UserDetailsServicespring security官方实现用户登录账号认证用户信息的,JwtUtils是封装jwt的相关操作,UserRepository是用的hibernate。
其中注解value中的配置信息是通过application.properties配置文件获取的如下是application.properties中的配置信息如下:
#jwt认证
scitc.jwt.header=Authorization
scitc.jwt.secret=mySecret
scitc.jwt.expiration=86400
scitc.jwt.tokenHead=Bearer
核心工具类jwtUtils如下:
@Component
public class JwtUtils {
public static final String ROLE_REFRESH_TOKEN = "ROLE_REFRESH_TOKEN";
private static final String CLAIM_KEY_USER_ID = "user_id";
private static final String CLAIM_KEY_AUTHORITIES = "scope";
@Autowired
private AuthorityRepository authorityRepository;
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);
@Value("${scitc.jwt.secret}")
private String secret;
@Value("${scitc.jwt.expiration}")
private Long access_token_expiration;
@Value("${scitc.jwt.expiration}")
private Long refresh_token_expiration;
private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
public User getUserFromToken(String token) {
User userDetail = getCurrentUser();;
try {
final Claims claims = getClaimsFromToken(token);
Integer userId = getUserIdFromToken(token);
String username = claims.getSubject();
String roleName = claims.get(CLAIM_KEY_AUTHORITIES).toString();
List<Authority> role = authorityRepository.findAuthoritiesByUserId(userId);
userDetail = new User(userId, username, role, "");
logger.info("Claims:" + claims);
logger.info("userId:" + userId);
logger.info("username:" + username);
logger.info("roleName:" + roleName);
logger.info("role:" + role);
logger.info("userDetail:" + userDetail);
} catch (Exception e) {
userDetail = null;
}
return userDetail;
}
public Integer getUserIdFromToken(String token) {
Integer userId;
try {
final Claims claims = getClaimsFromToken(token);
userId = (Integer) claims.get(((CLAIM_KEY_USER_ID)));
} catch (Exception e) {
userId = 0;
}
return userId;
}
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = claims.getIssuedAt();
} catch (Exception e) {
created = null;
}
return created;
}
public String generateAccessToken(User userDetail) {
Map<String, Object> claims = generateClaims(userDetail);
claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0));
return generateAccessToken(userDetail.getUsername(), claims);
}
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getCreatedDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token));
}
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
refreshedToken = generateAccessToken(claims.getSubject(), claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
public Boolean validateToken(String token, UserDetails userDetails) {
User userDetail = (User) userDetails;
final long userId = getUserIdFromToken(token);
final String username = getUsernameFromToken(token);
// final Date created = getCreatedDateFromToken(token);
return (userId == userDetail.getId()
&& username.equals(userDetail.getUsername())
&& !isTokenExpired(token)
// && !isCreatedBeforeLastPasswordReset(created, userDetail.getLastPasswordResetDate())
);
}
public String generateRefreshToken(User userDetail) {
Map<String, Object> claims = generateClaims(userDetail);
// 只授于更新 token 的权限
String roles[] = new String[]{JwtUtils.ROLE_REFRESH_TOKEN};
claims.put(CLAIM_KEY_AUTHORITIES, JSONUtil.toJSON(roles));
return generateRefreshToken(userDetail.getUsername(), claims);
}
public void putToken(String userName, String token) {
tokenMap.put(userName, token);
}
public void deleteToken(String userName) {
tokenMap.remove(userName);
}
public boolean containToken(String userName, String token) {
if (userName != null && tokenMap.containsKey(userName) && tokenMap.get(userName).equals(token)) {
return true;
}
return false;
}
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
private Date generateExpirationDate(long expiration) {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
private Map<String, Object> generateClaims(User userDetail) {
Map<String, Object> claims = new HashMap<>(16);
claims.put(CLAIM_KEY_USER_ID, userDetail.getId());
return claims;
}
private String generateAccessToken(String subject, Map<String, Object> claims) {
return generateToken(subject, claims, access_token_expiration);
}
private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
List<String> list = new ArrayList<>();
for (GrantedAuthority ga : authorities) {
list.add(ga.getAuthority());
}
return list;
}
private String generateRefreshToken(String subject, Map<String, Object> claims) {
return generateToken(subject, claims, refresh_token_expiration);
}
private String generateToken(String subject, Map<String, Object> claims, long expiration) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date())
.setExpiration(generateExpirationDate(expiration))
.compressWith(CompressionCodecs.DEFLATE)
.signWith(SIGNATURE_ALGORITHM, secret)
.compact();
}
}
CLAIM_KEY_USER_ID:代表的当前认证用户的id
CLAIM_KEY_AUTHORITIES:代表认证的信息
secret:是jwt构成之一
access_token_expiration:是jwt认证的期限和session一样有自己的过期时间
refresh_token_expiration:刷新token的和access_token_expiration是一样的作用
private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);
这句话的作用是保存token,我们知道token是由32位组成的。Map的key和value刚好满足需求。
下面讲解jwtUtils这个工具类的每个方法的作用:
1.
public User getUserFromToken(String token) {
try {
final Claims claims = getClaimsFromToken(token);
Integer userId = getUserIdFromToken(token);
String username = claims.getSubject();
String roleName = claims.get(CLAIM_KEY_AUTHORITIES).toString();
List<Authority> role = authorityRepository.findAuthoritiesByUserId(userId);
} catch (Exception e) {
userDetail = null;
}
return userDetail;
}
这个方法的作用是获取登录成功的token信息
Claims:是用来获取认证成功的用户信息
getUserIdFromToken:是 用来获取认证成功的用户id,单独的可以取值出来
claims.getSubject():是用来获取认证成功的用户名
claims.get(CLAIM_KEY_AUTHORITIES).toString():是用来获取认证的用户权限如:ROLE_USER,ROLE_ADMIN
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
这个方法是用来获取认证成功用户的用户名信息
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = claims.getIssuedAt();
} catch (Exception e) {
created = null;
}
return created;
}
//这个方法是用来获取认证成功后构建token成功返回构建成功的token日期
public String generateAccessToken(User userDetail) {
Map<String, Object> claims = generateClaims(userDetail);
claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0));
return generateAccessToken(userDetail.getUsername(), claims);
}
//这个方法是是用来访问token的信息
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
这个方法的作用是用来获取构建成功用户的信息。
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getCreatedDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token));
}
这段代码的作用是用来判断token是否已经失效。
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
refreshedToken = generateAccessToken(claims.getSubject(), claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
这段代码的作用是是用来刷新新的token。
public Boolean validateToken(String token, UserDetails userDetails) {
User userDetail = (User) userDetails;
final long userId = getUserIdFromToken(token);
final String username = getUsernameFromToken(token);
// final Date created = getCreatedDateFromToken(token);
return (userId == userDetail.getId()
&& username.equals(userDetail.getUsername())
&& !isTokenExpired(token)
// && !isCreatedBeforeLastPasswordReset(created, userDetail.getLastPasswordResetDate())
);
}
这段代码的作用是用来对token进行验证。
public String generateRefreshToken(User userDetail) {
Map<String, Object> claims = generateClaims(userDetail);
// 只授于更新 token 的权限
String roles[] = new String[]{JwtUtils.ROLE_REFRESH_TOKEN};
claims.put(CLAIM_KEY_AUTHORITIES, JSONUtil.toJSON(roles));
return generateRefreshToken(userDetail.getUsername(), claims);
}
这段代码的作用是用于权限的刷新。
public void putToken(String userName, String token) {
tokenMap.put(userName, token);
}
这段代码的作用是将当前用户认证后的token添加到map集合。
public void deleteToken(String userName) {
tokenMap.remove(userName);
}
这段代码作用用于用户退出时候,移除token
public boolean containToken(String userName, String token) {
if (userName != null && tokenMap.containsKey(userName) && tokenMap.get(userName).equals(token)) {
return true;
}
return false;
}
这段代码的作用是map中是否包含当前用户的token
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
这段代码做用是获取claims的属性
private Date generateExpirationDate(long expiration) {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
这段代码的作用是设置token的生命周期
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
这段代码作用是用来验证token是否失效
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
这段代码的作用是否用来验证最后一次登录的的时间
private Map<String, Object> generateClaims(User userDetail) {
Map<String, Object> claims = new HashMap<>(16);
claims.put(CLAIM_KEY_USER_ID, userDetail.getId());
return claims;
}
这段代码的作用是来创建 Claims的属性也就是登录成功用户的属性
private String generateAccessToken(String subject, Map<String, Object> claims) {
return generateToken(subject, claims, access_token_expiration);
}
这段代码是用于用户每次登录时候产生新的token
private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
List<String> list = new ArrayList<>();
for (GrantedAuthority ga : authorities) {
list.add(ga.getAuthority());
}
return list;
}
这段代码的作用获取登录用户的角色权限
private String generateRefreshToken(String subject, Map<String, Object> claims) {
return generateToken(subject, claims, refresh_token_expiration);
}
这段代码的作用是刷新每次登录的的token
private String generateToken(String subject, Map<String, Object> claims, long expiration) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date())
.setExpiration(generateExpirationDate(expiration))
.compressWith(CompressionCodecs.DEFLATE)
.signWith(SIGNATURE_ALGORITHM, secret)
.compact();
}
}
这段代码的作用是用来创建新的token。
下面是WebSecurityConfig是配置信息如下:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationEntryPoint unauthorizedHandler;
private final AccessDeniedHandler accessDeniedHandler;
private final UserDetailsService CustomUserDetailsService;
private final JwtAuthenticationTokenFilter authenticationTokenFilter;
@Autowired
public WebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler,
@Qualifier("RestAuthenticationAccessDeniedHandler") AccessDeniedHandler accessDeniedHandler,
@Qualifier("CustomUserDetailsService") UserDetailsService CustomUserDetailsService,
JwtAuthenticationTokenFilter authenticationTokenFilter) {
this.unauthorizedHandler = unauthorizedHandler;
this.accessDeniedHandler = accessDeniedHandler;
this.CustomUserDetailsService = CustomUserDetailsService;
this.authenticationTokenFilter = authenticationTokenFilter;
}
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
// 设置UserDetailsService
.userDetailsService(this.CustomUserDetailsService)
// 使用BCrypt进行密码的hash
.passwordEncoder(passwordEncoder());
}
/**
* 装载BCrypt密码编码器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// 对于获取token的rest api要允许匿名访问
.antMatchers("/api/v1/login", "/api/v1/sign", "/error/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 禁用缓存
httpSecurity.headers().cacheControl();
// 添加JWT filter
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) {
web
.ignoring()
.antMatchers(
"swagger-ui.html",
"**/swagger-ui.html",
"/favicon.ico",
"/**/*.css",
"/**/*.js",
"/**/*.png",
"/**/*.gif",
"/swagger-resources/**",
"/v2/**",
"/**/*.ttf"
);
web.ignoring().antMatchers("/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html"
);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
其中注意的是采用token认证,已经不需要session了。
下面来配置失败切入点:JwtAuthenticationEntryPoint
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
//验证为未登陆状态会进入此方法,认证错误
System.out.println("认证失败:" + authException.getMessage());
response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = response.getWriter();
String body = ResultJson.failure(ResultCode.UNAUTHORIZED, authException.getMessage()).toString();
printWriter.write(body);
printWriter.flush();
}
}
当认证进入此方法的时候说明登录失败。
下面是一个获取认证信息的tokenfilter:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.header}")
private String token_header;
@Resource
private JwtUtils jwtUtils;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String auth_token = request.getHeader(this.token_header);
final String auth_token_start = "Bearer ";
if (StringUtils.isNotEmpty(auth_token) && auth_token.startsWith(auth_token_start)) {
auth_token = auth_token.substring(auth_token_start.length());
} else {
// 不按规范,不允许通过验证
auth_token = null;
}
String username = jwtUtils.getUsernameFromToken(auth_token);
logger.info(String.format("Checking authentication for userDetail %s.", username));
if (jwtUtils.containToken(username, auth_token) && username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetail userDetail = jwtUtils.getUserFromToken(auth_token);
if (jwtUtils.validateToken(auth_token, userDetail)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info(String.format("Authenticated userDetail %s, setting security context", username));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
当权限不足时候执行如下方法:
@Component("RestAuthenticationAccessDeniedHandler")
public class RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
//登陆状态下,权限不足执行该方法
System.out.println("权限不足:" + e.getMessage());
response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = response.getWriter();
String body = ResultJson.failure(ResultCode.FORBIDDEN, e.getMessage()).toString();
printWriter.write(body);
printWriter.flush();
}
下面是数据库表结构:
其中sys_user是用户表,sys_role是角色表 sys_user_role是是鉴权表。
最后是控制器controller代码如下:
@RestController
@Api(description = "登陆注册及刷新token")
@RequestMapping("/api/v1")
public class AuthController {
@Value("${jwt.header}")
private String tokenHeader;
private final AuthService authService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping(value = "/login")
@ApiOperation(value = "登陆", notes = "登陆成功返回token,测试管理员账号:admin,123456;用户账号:les123,admin")
public ResultJson<ResponseUserToken> login(
@Valid @RequestBody User user){
final ResponseUserToken response = authService.login(user.getName(), user.getPassword());
return ResultJson.ok(response);
}
@GetMapping(value = "/logout")
@ApiOperation(value = "登出", notes = "退出登陆")
@ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")})
public ResultJson logout(HttpServletRequest request){
String token = request.getHeader(tokenHeader);
if (token == null) {
return ResultJson.failure(ResultCode.UNAUTHORIZED);
}
authService.logout(token);
return ResultJson.ok();
}
@RequestMapping(value = "/user",method = {RequestMethod.POST,RequestMethod.GET})
@ApiOperation(value = "根据token获取用户信息", notes = "根据token获取用户信息")
@ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")})
public ResultJson getUser(HttpServletRequest request){
String token = request.getHeader(tokenHeader);
if (token == null) {
return ResultJson.failure(ResultCode.UNAUTHORIZED);
}
UserDetail userDetail = authService.getUserByToken(token);
logger.info("userDetail:" + userDetail);
return ResultJson.ok(userDetail);
}
@PostMapping(value = "/sign")
@ApiOperation(value = "用户注册")
public ResultJson sign(@RequestBody User user) {
if (StringUtils.isAnyBlank(user.getName(), user.getPassword())) {
return ResultJson.failure(ResultCode.BAD_REQUEST);
}
UserDetail userDetail = new UserDetail(user.getName(), user.getPassword(), Role.builder().id(1l).build());
return ResultJson.ok(authService.register(userDetail));
}
@GetMapping(value = "refresh")
@ApiOperation(value = "刷新token")
public ResultJson refreshAndGetAuthenticationToken(
HttpServletRequest request){
String token = request.getHeader(tokenHeader);
ResponseUserToken response = authService.refresh(token);
System.out.println("response:" + response);
if(response == null) {
return ResultJson.failure(ResultCode.BAD_REQUEST, "token无效");
} else {
return ResultJson.ok(response);
}
}
该demogithub地址如下:
https://github.com/zhoubiao188/springboot_mybatis_security_token