spring security jwt 简单使用
首先要定义一个实体类UserDetail.java;并集成org.springframework.security.core.userdetails.UserDetails
类;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author javal
* createAt: 2021/11/09
*/
@Data
public class UserDetail implements UserDetails {
private Long id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
private String inrOrgNo;
/** 片区名称 */
private String orgVstdArea;
/** 支行名称 */
private String orgShrtNm;
public UserDetail(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
public UserDetail() {
}
public UserDetail(String username) {
this.username = username;
}
public UserDetail(Long id, String username, String password, Collection<? extends GrantedAuthority> authorities, String inrOrgNo, String orgVstdArea, String orgShrtNm) {
this.id = id;
this.username = username;
this.password = password;
this.authorities = authorities;
this.inrOrgNo = inrOrgNo;
this.orgVstdArea = orgVstdArea;
this.orgShrtNm = orgShrtNm;
}
private List<GrantedAuthority> mapToGrantedAuthorities(List<String> authorities) {
return authorities.stream()
.map(authority -> new SimpleGrantedAuthority(authority))
.collect(Collectors.toList());
}
private List<GrantedAuthority> mapToGrantedAuthorities(String authoritie) {
return Arrays.asList(new SimpleGrantedAuthority(authoritie));
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
public String getInrOrgNo() {
return inrOrgNo;
}
public void setInrOrgNo(String inrOrgNo) {
this.inrOrgNo = inrOrgNo;
}
public String getOrgVstdArea() {
return orgVstdArea;
}
public void setOrgVstdArea(String orgVstdArea) {
this.orgVstdArea = orgVstdArea;
}
public String getOrgShrtNm() {
return orgShrtNm;
}
public void setOrgShrtNm(String orgShrtNm) {
this.orgShrtNm = orgShrtNm;
}
}
定义接口org.springframework.security.core.userdetails.UserDetailsService
实现类UserDetailsServiceImpl.java,众所周知,该接口只有一个方法loadUserByUsername
,通过该方法使用用户名称查询用户信息。并返回org.springframework.security.core.userdetails.UserDetails.UserDetailsService
对象。即该对象为返回的实际用户信息。
import com.gbicc.framework.entity.UserDetail;
import com.gbicc.system.entity.SysUser;
import com.gbicc.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
SysUserMapper sysUserMapper;
@Override
public UserDetail loadUserByUsername(String name) throws UsernameNotFoundException {
SysUser user = sysUserMapper.loadUserByUsername(name);
if (user == null){
throw new UsernameNotFoundException(String.format("'%s'.这个用户不存在", name));
}else {
//todo 用户的角色和权限都是放置在 GrantedAuthority 中的;角色带ROLE_,权限没有(target:history:list)。
List<GrantedAuthority> authorityList = new ArrayList<>();//设置用户权限
if (user.getInrOrgNo().equals("0")){
authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));//普通用户
}else {
authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));//超级管理员
}
UserDetail detail = new UserDetail(user.getUserName(),user.getPassword(),authorityList);//正儿八经返回数据内容;
return detail;
}
}
}
贴一下org.springframework.security.core.userdetails.UserDetails.UserDetailsService
接口的源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
定义filter拦截器jwt的拦截器JwtAuthenticationTokenFilter.java集成类OncePerRequestFilter
import com.gbicc.framework.security.utils.JwtTokenUtil;
import com.gbicc.framework.security.config.UserDetailsServiceImpl;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* token校验
* @author: javal
* createAt: 2018/9/14
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.header}")
private String token_header;
@Resource
private UserDetailsServiceImpl userDetailsService;
@Resource
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String requestHeader = request.getHeader(this.token_header);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (ExpiredJwtException e) {
}
}
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());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
定义认证失败无权限处理类JwtAuthenticationEntryPoint
import com.gbicc.framework.entity.ResultCode;
import com.gbicc.framework.entity.ResultJson;
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;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
/**
* @author: javal
* createAt: 2018/9/20
*/
@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();
}
}
定义退出功能接口类LogoutSuccessHandlerImpl,(可以选择不定义)
import com.gbicc.common.utils.ServletUtils;
import com.gbicc.framework.entity.UserDetail;
import com.gbicc.monitor.mapper.SysLogininforMapper;
import com.gbicc.monitor.entity.SysLogininfor;
import com.gbicc.system.service.LoginService;
import eu.bitwalker.useragentutils.UserAgent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
LoginService loginService;
@Autowired
SysLogininforMapper logininforMapper;
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
UserDetail user = loginService.getUserByToken(httpServletRequest.getHeader(tokenHeader));
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
String ip = "unknown";
if (httpServletRequest == null)
{
ip= "unknown";
}
ip = httpServletRequest.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = httpServletRequest.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = httpServletRequest.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = httpServletRequest.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = httpServletRequest.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = httpServletRequest.getRemoteAddr();
}
ip="0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(user.getUsername());
logininfor.setIpaddr(ip);
logininfor.setOs(os);
logininfor.setBrowser(browser);
logininfor.setMsg("退出成功");
logininfor.setStatus("0");
logininforMapper.insertSysLogininfor(logininfor);
String token = httpServletRequest.getHeader(tokenHeader);
loginService.logout(token);
}
}
最后贴上seerurity的配置类(核心配置类)
import com.gbicc.framework.security.handle.JwtAuthenticationEntryPoint;
import com.gbicc.framework.security.handle.LogoutSuccessHandlerImpl;
import com.gbicc.framework.security.security.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author javal
* createAt: 2021/11/09
*/
@Configuration
@EnableWebSecurity //这个注解必须加,开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true) //保证post之前的注解可以使用
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/***
* 实现AuthenticationEntryPoint这个接口,发现没有凭证,往response中放些东西。
*/
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
/**
* 用户认证类
*/
@Autowired
private UserDetailsService userDetailsService;
/***
* TOKEN认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 装载BCrypt密码编码器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 身份认证接口;先来这里认证一下
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//拦截在这配
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
//这里面主要配置如果没有凭证,可以进行一些操作。
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
//todo /ststem模块只能被ADMIN角色或者USER角色中任意角色就访问
.antMatchers("/ststem/**").hasAnyRole("ADMIN","USER")
// 对于获取token的rest api要允许匿名访问
.antMatchers("/login/**","/getInfo/**","/common/**").permitAll()
.antMatchers("/api/v1/login", "/api/v1/sign", "/error/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 禁用缓存
httpSecurity.headers().cacheControl();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
/**
* 解决 无法直接注入 AuthenticationManager
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
application.yml配置文件
server:
port: 8087
gbicc:
profile: D:/iuoooo/uploadPath
# profile: /usr/loacl/iuoooo/uploadPath
# PageHelper分页插件
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
spring:
application:
name: digital
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/digitalize_bank?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 10 # 最小连接数
max-active: 20 # 最大连接数
max-wait: 60000 # 获取连接时的最大等待时间
min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间,单位是毫秒
time-between-eviction-runs-millis: 60000 # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒
filters: stat,wall # 配置扩展插件:stat-监控统计,log4j-日志,wall-防火墙(防止SQL注入),去掉后,监控界面的sql无法统计
validation-query: SELECT 1 # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效
test-on-borrow: true # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能
test-on-return: true # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能
test-while-idle: true # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能
stat-view-servlet:
enabled: true # 是否开启 StatViewServlet
allow: 127.0.0.1 # 访问监控页面 白名单,默认127.0.0.1
deny: 192.168.56.1 # 访问监控页面 黑名单
login-username: admin # 访问监控页面 登陆账号
login-password: admin # 访问监控页面 登陆密码
filter:
stat:
enabled: true # 是否开启 FilterStat,默认true
log-slow-sql: true # 是否开启 慢SQL 记录,默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000,单位:毫秒
merge-sql: false # 合并多个连接池的监控数据,默认false
# 输出sql语句日志
logging:
level:
com.gbicc: debug
# MyBatis配置
mybatis:
# 指定扫描包名不生效~~~
#type-aliases-package: com.gbicc.framework.entity,com.gbicc.monitor.entity,com.gbicc.quarzt.entity,com.gbicc.system.entity
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath*:mybatis/**/*Mapper.xml
# JWT
jwt:
header: Authorization
secret: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
#token有效期一天
expiration: 86400
tokenHead: "Bearer "
jwt快捷工具类(这个建议集成redis),本工具类未集成redis。因此将用户信息存储至SecurityContextHolder.getContext().setAuthentication(authentication);
中,可在JwtAuthenticationTokenFilter
类中看到。
package com.gbicc.framework.security.utils;
import com.gbicc.framework.entity.UserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.token}")
private String tokenHeader;
private Clock clock = DefaultClock.INSTANCE;
//token存储容器
Map<String,String> tokenMap=new ConcurrentHashMap<>(32);
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration);
}
public Boolean validateToken(String token, UserDetails userDetails) {
UserDetail user = (UserDetail) userDetails;
final String username = getUsernameFromToken(token);
return (username.equals(user.getUsername())
&& !isTokenExpired(token)
);
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
public UserDetail getUserFromToken(String token){
UserDetail userDetail;
try {
Claims claims=getAllClaimsFromToken(token);
String username=claims.getSubject();
if (!username.equals("")){
Authentication authentication=SecurityContextHolder.getContext().getAuthentication();
userDetail = (UserDetail) authentication.getPrincipal();
}else {
userDetail=new UserDetail();
}
}catch (Exception e){
userDetail=null;
}
return userDetail;
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
public void putToken(String userName,String token){
tokenMap.put(userName,token);
}
public void deleteToken(String userName){
tokenMap.remove(userName);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
}
OK,结束,未能详细介绍;有时间补充一下。