JWT基本概念
JWT生成的token由三部分组成:
-
头部:主要设置一些规范信息,签名部分的编码格式就在头部中声明。
-
载荷:token中存放有效信息的部分,比如用户名,用户角色,过期时间等,但是不要放密码,会泄露!
-
签名:将头部与载荷分别采用base64编码后,用“.”相连,再加入盐,最后使用头部声明的编码类型进行编码,就得到了签名。
Rsa基本概念
基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
-
私钥加密,持有私钥或公钥才可以解密
-
公钥加密,持有私钥才可解密
优点:安全,难以破解
缺点:算法比较耗时,为了安全,可以接受
历史:三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三
个人的名字缩写:RSA。
文章涉及工具类
认证服务
引入依赖
列出主要安全依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.5</version>
</dependency>
配置application.yml
manager:
jwt:
secret: ea61b46dse2@manager@9ds966@codekiller@33da # 登录校验的密钥
pubKeyPath: E:\chrome\token\\rsa.pub # 公钥地址
priKeyPath: E:\chrome\token\\rsa.pri # 私钥地址
expire: 30 # 过期时间,单位分钟
headerName: Authorization #token的名称
配置properties
package top.codekiller.test.springsecurity.properties;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import top.codekiller.test.springsecurity.utils.RsaUtils;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* @author codekiller
* @date 2020/5/22 13:30
*
* 公钥和私钥的配置类
*/
@ConfigurationProperties(prefix = "manager.jwt")
@Slf4j
@Data
public class JwtProperties {
/**
* 密钥
*/
private String secret;
/**
* 公钥保存路径
*/
private String pubKeyPath;
/**
* 私钥保存路径
*/
private String priKeyPath;
/**
* token过期时间
*/
private int expire;
/**
* 公钥
*/
private PublicKey publicKey;
/**
* 私钥
*/
private PrivateKey privateKey;
/**
* token名称
*/
private String headerName;
/**
* @PostContruct:在构造方法执行之后执行该方法
* 创建私钥和公钥,并且获取赋值
*/
@PostConstruct
public void init(){
try {
File pubKey = new File(pubKeyPath);
File priKey = new File(priKeyPath);
if (!pubKey.exists() || !priKey.exists()) {
// 生成公钥和私钥
RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
}
// 获取公钥和私钥
this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
} catch (Exception e) {
log.error("初始化公钥和私钥失败!", e);
throw new RuntimeException();
}
}
}
实体类
SysUser (UserDetails)
@Data
public class SysUser implements UserDetails {
private Integer id;
private String username;
private String password;
private Integer status;
private List<SysRole> roles;
/**
* 权限集合
* @return
*/
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
/**
* 账号失效
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 账号锁定
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 密码失效
* @return
*/
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用
* @return
*/
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
SysRole(GrantedAuthority)
@Data
public class SysRole implements GrantedAuthority {
private Integer id;
private String roleName;
private String roleDesc;
@JsonIgnore
@Override
public String getAuthority() {
return this.roleName;
}
}
mapper
RoleMapper
public interface RoleMapper {
@Select("select r.id,r.role_name,r.role_desc from sys_role r,sys_user_role ur where r.id=ur.rid and ur.uid=#{uid} ")
List<SysRole> findByUid(Integer uid);
}
UserMapper
public interface UserMapper {
@Select("select * from sys_user where username=#{name}")
@Results({
@Result(id=true,property = "id",column = "id"),
@Result(property = "roles",column = "id",javaType = List.class,
many = @Many(select = "top.codekiller.test.springsecurity.mapper.RoleMapper.findByUid"))
})
SysUser findByName(String name);
}
UserService
登录的验证
public interface IUserService extends UserDetailsService {
}
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return this.userMapper.findByName(s);
}
}
WebSecurityConfig
security的配置类
@EnableConfigurationProperties(JwtProperties.class)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true) //开启注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IUserService userService;
@Autowired
private JwtProperties jwtProperties;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 验证用户的来源[内存,数据库]
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//数据库指定
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder());
}
/**
* 配置springSecurity相关信息
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//释放静态资源,指定资源拦截规则,指定自定义认证页面,指定退出认证配置,csrf
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/**").hasAnyRole("USER","ADMIN")
.anyRequest()
.authenticated()
.and()
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.permitAll()
.and()
.addFilter(new JwtAccreditFilter(super.authenticationManager(),this.jwtProperties))
.addFilter(new JwtVerifyFilter(super.authenticationManager(),this.jwtProperties))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
编写过滤器
JwtAccreditFilter
/**
* @author codekiller
* @date 2020/6/8 19:17
* @description 登录过滤器
*/
@Slf4j
public class JwtAccreditFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private JwtProperties jwtProperties;
private ObjectMapper objectMapper=new ObjectMapper();
public JwtAccreditFilter(AuthenticationManager authenticationManager, JwtProperties jwtProperties) {
this.authenticationManager = authenticationManager;
this.jwtProperties = jwtProperties;
}
/**
* 接受并解析用户凭证
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
SysUser sysUser = objectMapper.readValue(request.getInputStream(), SysUser.class);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
return this.authenticationManager.authenticate(authRequest);
} catch (IOException e) {
PrintWriter writer=null;
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
writer= response.getWriter();
Map<String,Object> map=new HashMap<>(16);
map.put("result_code",HttpStatus.UNAUTHORIZED.value());
map.put("result_reason","用户名或者密码错误");
writer.write(JsonUtils.serialize(map));
writer.flush();
} catch (IOException ex) {
log.error("登录出错",e);
}finally {
if(writer!=null){
writer.close();
}
}
throw new RuntimeException(e);
}
}
/**
* 进行授权
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SysUser user=new SysUser();
user.setUsername(authResult.getName());
user.setRoles((List<SysRole>)authResult.getAuthorities());
try {
String token = JwtUtils.generateTokenExpireInMinutes(user, this.jwtProperties.getPrivateKey(), this.jwtProperties.getExpire()*60);
response.addHeader(this.jwtProperties.getHeaderName(), "Bearer " + token);
} catch (Exception e) {
PrintWriter writer=null;
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.OK.value());
writer= response.getWriter();
Map<String,Object> map=new HashMap<>(16);
map.put("result_code",HttpStatus.OK.value());
map.put("result_reason","认证通过");
writer.write(JsonUtils.serialize(map));
writer.flush();
} catch (IOException ex) {
log.error("授权失败",e);
}finally {
if(writer!=null){
writer.close();
}
}
throw new RuntimeException(e);
}
}
}
JwtVerifyFilter
/**
* @author codekiller
* @date 2020/6/8 20:18
* @description 认证过滤器
*/
@Slf4j
public class JwtVerifyFilter extends BasicAuthenticationFilter {
private JwtProperties jwtProperties;
public JwtVerifyFilter(AuthenticationManager authenticationManager, JwtProperties jwtProperties) {
super(authenticationManager);
this.jwtProperties = jwtProperties;
}
/**
* 进行认证
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header=request.getHeader("Authorization");
//如果没有认证
if(StringUtils.isBlank(header)){
chain.doFilter(request,response);
this.responseJson(response);
}else{
//携带正确格式的token
String token = header.replace("Bearer ", "");
System.out.println("token"+token);
try {
Payload<SysUser> payload = JwtUtils.getInfoFromToken(token, this.jwtProperties.getPublicKey(),SysUser.class);
SysUser user=payload.getUserInfo();
if(user!=null){
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(user.getUsername(),null,user.getRoles());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request,response);
}
} catch (Exception e) {
log.error("认证出错",e);
}
}
}
/**
* 认证失败响应的json
* @param response
*/
private void responseJson(HttpServletResponse response) {
PrintWriter writer=null;
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.FORBIDDEN.value());
writer= response.getWriter();
Map<String,Object> map=new HashMap<>(16);
map.put("result_code",HttpStatus.FORBIDDEN.value());
map.put("result_reason","请登录!");
writer.write(JsonUtils.serialize(map));
writer.flush();
} catch (IOException ex) {
log.error("认证发送json数据IO错误",ex);
}finally {
if (writer != null) {
writer.close();
}
}
}
}
资源服务
实体类
和认证服务的一样,User和Role连个必要实体类
WebSecurityConfig
@EnableConfigurationProperties(JwtProperties.class)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true) //开启注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtProperties jwtProperties;
/**
* 配置springSecurity相关信息
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//指定资源拦截规则,指定退出认证配置,csrf
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/product").hasAnyRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.addFilter(new JwtVerifyFilter(super.authenticationManager(),this.jwtProperties))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
配置application.yml
manager:
jwt:
pubKeyPath: E:\chrome\token\\rsa.pub # 公钥地址
headerName: Authorization
配置properties
/**
* @author codekiller
* @date 2020/5/27 0:30
* @description token配置类
*/
@ConfigurationProperties(prefix = "manager.jwt")
@Slf4j
@Data
public class JwtProperties {
/**
* 公钥
*/
private PublicKey publicKey;
/**
* 公钥地址
*/
private String pubKeyPath;
/**
* token的请求头名称
*/
private String headerName;
@PostConstruct
public void init(){
try {
// 获取私钥
this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
} catch (Exception e) {
log.error("初始化公钥失败!", e);
throw new RuntimeException();
}
}
}
认证过滤器(JwtVerifyFilter)
/**
* @author codekiller
* @date 2020/6/8 20:18
* @description 认证过滤器
*/
@Slf4j
public class JwtVerifyFilter extends BasicAuthenticationFilter {
private JwtProperties jwtProperties;
public JwtVerifyFilter(AuthenticationManager authenticationManager, JwtProperties jwtProperties) {
super(authenticationManager);
this.jwtProperties = jwtProperties;
}
/**
* 进行认证
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header=request.getHeader(this.jwtProperties.getHeaderName());
//如果没有认证
if(StringUtils.isBlank(header)){
chain.doFilter(request,response);
this.responseJson(response);
}else{
//携带正确格式的token
String token = header.replace("Bearer ", "");
System.out.println("token"+token);
try {
Payload<SysUser> payload = JwtUtils.getInfoFromToken(token, this.jwtProperties.getPublicKey(),SysUser.class);
SysUser user=payload.getUserInfo();
if(user!=null){
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(user.getUsername(),null,user.getRoles());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request,response);
}
} catch (Exception e) {
log.error("认证出错",e);
}
}
}
/**
* 认证失败响应的json
* @param response
*/
private void responseJson(HttpServletResponse response) {
PrintWriter writer=null;
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.FORBIDDEN.value());
writer= response.getWriter();
Map<String,Object> map=new HashMap<>(16);
map.put("result_code",HttpStatus.FORBIDDEN.value());
map.put("result_reason","请登录!");
writer.write(JsonUtils.serialize(map));
writer.flush();
} catch (IOException ex) {
log.error("认证发送json数据IO错误",ex);
}finally {
if (writer != null) {
writer.close();
}
}
}
}
结果
写一个接口进行测试
@RestController
@RequestMapping("/product")
public class ProductController {
@Secured({"ROLE_ADMIN"}) //控制权限访问
@RequestMapping("/findAll")
public String findAll(){
return "product-list";
}
}
进行登录,获取token
访问资源服务