一、jar包依赖:
<!--jwt-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<optional>true</optional>
</dependency>
二、表结构初及数据始化:
CREATE TABLE IF NOT EXISTS `rbac_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`age` int(5) DEFAULT NULL COMMENT '年龄',
`name` varchar(255) DEFAULT NULL COMMENT '姓名',
`birth` datetime DEFAULT NULL COMMENT '生日',
`account` varchar(20) NOT NULL COMMENT '账号',
`password` varchar(500) NOT NULL COMMENT '密码',
`nick_name` varchar(20) COMMENT '昵称',
`email` varchar(100) COMMENT '邮箱',
`mobile_phone` varchar(20) COMMENT '手机',
`avatar` varchar(500) COMMENT '头像地址',
last_password_reset_date datetime DEFAULT NULL COMMENT '密码更新时间',
status char(1) default 'A' COMMENT '状态:A 正常,D 删除',
`version` bigint(20) DEFAULT '1' COMMENT '版本号',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`created_by` bigint(20) DEFAULT NULL COMMENT '创建人',
`updated_time` datetime DEFAULT NULL COMMENT '更新时间',
`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
-- 建表sql(role)
CREATE TABLE IF NOT EXISTS `rbac_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT ,
`code` varchar(50) NOT NULL COMMENT '代码',
`name` varchar(100) NOT NULL COMMENT '名称',
`description` varchar(100) COMMENT '描述',
status char(1) default 'A' COMMENT '状态:A 正常,D 删除',
`version` bigint(20) DEFAULT '1' COMMENT '版本号',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`created_by` bigint(20) DEFAULT NULL COMMENT '创建人',
`updated_time` datetime DEFAULT NULL COMMENT '更新时间',
`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';
-- 建表sql(permission)
CREATE TABLE IF NOT EXISTS `rbac_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT ,
`code` varchar(50) NOT NULL COMMENT '代码',
`name` varchar(100) NOT NULL COMMENT '名称',
`description` varchar(100) ,
status char(1) default 'A' COMMENT '状态:A 正常,D 删除',
`version` bigint(20) DEFAULT '1' COMMENT '版本号',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`created_by` bigint(20) DEFAULT NULL COMMENT '创建人',
`updated_time` datetime DEFAULT NULL COMMENT '更新时间',
`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='权限表';
-- 建表sql(user_role)
CREATE TABLE IF NOT EXISTS `rbac_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT ,
`user_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
status char(1) default 'A' COMMENT '状态:A 正常,D 删除',
`version` bigint(20) DEFAULT '1' COMMENT '版本号',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`created_by` bigint(20) DEFAULT NULL COMMENT '创建人',
`updated_time` datetime DEFAULT NULL COMMENT '更新时间',
`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色表';
-- 建表sql(role_permission)
CREATE TABLE IF NOT EXISTS `rbac_role_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT ,
`role_id` bigint(20) NOT NULL,
`permission_id` bigint(20) NOT NULL,
status char(1) default 'A' COMMENT '状态:A 正常,D 删除',
`version` bigint(20) DEFAULT '1' COMMENT '版本号',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`created_by` bigint(20) DEFAULT NULL COMMENT '创建人',
`updated_time` datetime DEFAULT NULL COMMENT '更新时间',
`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限表';
-- 初始化用户
INSERT INTO `gourd`.`rbac_user`(`id`, `age`, `name`, `birth`, `account`, `password`, `nick_name`, `email`, `last_password_reset_date`, `mobile_phone`, `avatar`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (1, 0, 'admin', '2019-04-25 18:43:07', 'admin', '$2a$10$o1avyPI98TdBco3m7JgCTuhPQaasSy/J2EqDH9XX46rxggjviWMzO', 'admin', NULL, NULL, '13584278267', NULL, 'A', 1, NULL, NULL, NULL, NULL);
INSERT INTO `gourd`.`rbac_user`(`id`, `age`, `name`, `birth`, `account`, `password`, `nick_name`, `email`, `last_password_reset_date`, `mobile_phone`, `avatar`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (2, 0, 'gourd', '2019-04-08 09:06:56', 'gourd', '$2a$10$o1avyPI98TdBco3m7JgCTuhPQaasSy/J2EqDH9XX46rxggjviWMzO', 'gourd', NULL, '2019-04-08 09:12:56', '13584278267', NULL, 'A', 1, NULL, NULL, NULL, NULL);
-- 初始化角色
INSERT INTO `gourd`.`rbac_role`(`id`, `code`, `name`, `description`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`, `status`) VALUES (1, 'ADMIN', '管理员', NULL, 1, NULL, NULL, NULL, NULL, 'A');
INSERT INTO `gourd`.`rbac_role`(`id`, `code`, `name`, `description`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`, `status`) VALUES (2, 'EMPLOYEE', '员工', NULL, 1, NULL, NULL, NULL, NULL, 'A');
INSERT INTO `gourd`.`rbac_role`(`id`, `code`, `name`, `description`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`, `status`) VALUES (3, 'USER', '用户', '注册的普通用户', 1, NULL, NULL, NULL, NULL, 'A');
-- 初始化用户权限
INSERT INTO `gourd`.`rbac_user_role`(`id`, `user_id`, `role_id`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`, `status`) VALUES (1, 1, 1, 0, NULL, NULL, NULL, NULL, 'A');
INSERT INTO `gourd`.`rbac_user_role`(`id`, `user_id`, `role_id`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`, `status`) VALUES (2, 2, 3, 0, NULL, NULL, NULL, NULL, 'A');
-- 初始化权限
INSERT INTO `gourd`.`rbac_permission`(`id`, `code`, `name`, `description`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (1, 'READ', '阅读权限', NULL, 'A', 1, NULL, NULL, NULL, NULL);
INSERT INTO `gourd`.`rbac_permission`(`id`, `code`, `name`, `description`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (2, 'WRITE', '写权限', NULL, 'A', 1, NULL, NULL, NULL, NULL);
-- 初始化角色权限
INSERT INTO `gourd`.`rbac_role_permission`(`id`, `role_id`, `permission_id`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (1, 1, 1, 'A', 1, NULL, NULL, NULL, NULL);
INSERT INTO `gourd`.`rbac_role_permission`(`id`, `role_id`, `permission_id`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (2, 1, 2, 'A', 1, NULL, NULL, NULL, NULL);
INSERT INTO `gourd`.`rbac_role_permission`(`id`, `role_id`, `permission_id`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (3, 2, 1, 'A', 1, NULL, NULL, NULL, NULL);
INSERT INTO `gourd`.`rbac_role_permission`(`id`, `role_id`, `permission_id`, `status`, `version`, `created_time`, `created_by`, `updated_time`, `updated_by`) VALUES (4, 3, 1, 'A', 1, NULL, NULL, NULL, NULL);
三、配置项及配置类
spring:
#是否开启spring security基本的鉴权
security.basic.enabled: true
# JWT
jwt:
header: jwt-token
secret: mySecret
#token有效期一天(秒)
expiration: 86400
核心配置类
import com.gourd.common.exception.GoAuthenticationEntryPoint;
import com.gourd.common.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
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.builders.WebSecurity;
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: gourd.
* @Description: SpringSecurity 核心配置类
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private UserDetailsService userDetailsService;
@Value("${jwt.expiration}")
private int validate;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加自定义的userDetailsService认证
auth.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
}
/**
* 装载BCrypt密码编码器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors().and()
// 使用 JWT,关闭token
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
// 未经过认证的用户访问受保护的资源
.authenticationEntryPoint(new GoAuthenticationEntryPoint())
.and()
.authorizeRequests()
// 任何用户都可以访问URL以"/resources/", equals "/signup", 或者 "/about"开头的URL。
//以 "/admin/" 开头的URL只能由拥有 "ROLE_ADMIN"角色的用户访问。请注意我们使用 hasRole 方法
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/user").access("hasRole('USER') or hasRole('ADMIN') ")
.antMatchers("/employee").access("hasRole('EMPLOYEE') or hasRole('ADMIN') ")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/index.html")
.defaultSuccessUrl("/index.html", true).failureUrl("/error.html")
.permitAll()
.and()
.logout()
.logoutUrl("/login/logout")
.permitAll()
// 防止iframe 造成跨域
.and().headers().frameOptions().disable();
// 记住我
http.rememberMe().rememberMeParameter("remember-me")
.userDetailsService(userDetailsService).tokenValiditySeconds(validate);
http.exceptionHandling()
.and().addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/",
"/dic/**",
"/nacos/**",
"/mail/**",
"/excel/**",
"/file/**",
"/spider/**",
"/websocket/**",
"/user-es/**",
"/socket/**",
"/feign/**",
"/eureka/**",
"/quartz/**",
"/gourd/**",
"/kafka/**",
"/openOffice/**",
"/login/**",
"/auth/**",
"/api/**",
"/resources/**",
"/templates/**",
"/druid/*",
"/login.html",
"/websocket.html",
"/error.html",
"/file.html",
"*.css",
"*.js",
"*.gif","*.jpg", "*.png", "*.ico",
"/swagger-ui.html",
"/swagger-resources/**",
"/v2/**",
"/webjars/**");
}
}
拦截器:
import com.gourd.common.exception.ServiceException;
import com.gourd.common.rbac.vo.JwtUser;
import com.gourd.common.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
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;
import java.util.Collection;
/**
* @author gourd
*/
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
private JwtUtil jwtTokenUtil;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authToken = request.getHeader(this.tokenHeader);
// 获取用户信息
UserDetails userDetails = jwtTokenUtil.getUserFromToken(authToken);
if(userDetails != null){
String username = ((JwtUser) userDetails).getAccount();
log.info("checking authentication " + username);
if (username != null && jwtTokenUtil.containToken(username, authToken) && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtTokenUtil.validateToken(authToken)) {
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, authorities);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
log.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}else {
throw new ServiceException("token失效,请重新登录");
}
}
}
chain.doFilter(request, response);
}
}
四、权限相关基本类
1.身份验证异常处理类
import com.alibaba.fastjson.JSON;
import com.gourd.common.data.BaseResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author: gourd.
* @Description: 它负责启动未经过身份验证的用户的身份验证过程(当他们试图访问受保护的资源
* @Date:Created in 2018/8/25 23:11.
*/
public class GoAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
BaseResponse baseResponse =new BaseResponse(HttpStatus.UNAUTHORIZED.value(),"身份认证失败,请重新登录");
httpServletResponse.getWriter().write(JSON.toJSONString(baseResponse));
httpServletResponse.getWriter().flush();
}
}
2.userDetails类 核心用户类
import com.gourd.common.rbac.entity.RbacUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.*;
/**
* @author gourd
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtUser implements UserDetails {
private Long id;
private String name;
private String account;
private String password;
/**
* 密码更新时间
*/
private Date lastPasswordResetDate;
/**
* 权限
*/
private Collection<? extends GrantedAuthority> authorities;
/**
* 写一个能直接使用user创建jwtUser的构造器
* @param user
*/
public JwtUser(RbacUser user) {
this.id = user.getId();
this.name = user.getName();
this.account = user.getAccount();
this.password = user.getPassword();
this.lastPasswordResetDate = user.getLastPasswordResetDate();
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = new HashSet<>();
if(CollectionUtils.isNotEmpty(user.getAuthorities())){
for(String code : user.getAuthorities()){
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(code);
simpleGrantedAuthorities.add(simpleGrantedAuthority);
}
}
this.authorities = simpleGrantedAuthorities;
}
public JwtUser(Long id,String name,String account,List<String> authorities) {
this.id =id;
this.name = name;
this.account = account;
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = new HashSet<>();
if(CollectionUtils.isNotEmpty(authorities)){
for(String code : authorities){
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(code);
simpleGrantedAuthorities.add(simpleGrantedAuthority);
}
}
this.authorities = simpleGrantedAuthorities;
}
/**
* 获取权限信息,目前只会拿来存角色
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return account;
}
@Override
public boolean isAccountNonExpired() {
// 账号是否未过期,默认是false,记得要改一下
return true;
}
@Override
public boolean isAccountNonLocked() {
// 账号是否未锁定,默认是false,记得也要改一下
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// 账号凭证是否未过期,默认是false,记得还要改一下
return true;
}
@Override
public boolean isEnabled() {
// 这个有点抽象不会翻译,默认也是false,记得改一下
return true;
}
}
3、登录成功返回对象
package com.gourd.web.rbac.auth;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author gourd
* createAt: 2018/9/17
*/
@Data
@AllArgsConstructor
public class ResponseUserToken {
private String token;
private JwtUser userDetail;
}
五、具体工具类
import com.gourd.common.rbac.vo.JwtUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: gourd
* createAt: 2018/9/14
*/
@Component
@RefreshScope
@Slf4j
public class JwtUtil {
private static final String CLAIM_KEY_ACCOUNT = "account";
private static final String CLAIM_KEY_USERNAME = "name";
private static final String CLAIM_KEY_CREATED = "created";
private static final String CLAIM_KEY_USER_ID = "id";
private static final String CLAIM_KEY_AUTHORITIES = "scope";
private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long access_token_expiration;
@Value("${jwt.expiration}")
private Long refresh_token_expiration;
private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
/**
* 获取当用户
* @return
*/
public static JwtUser getUserInfo(){
JwtUser user = (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return user;
}
/**
* 根据token获取用户信息
* @param token
* @return
*/
public JwtUser getUserFromToken(String token) {
JwtUser userDetail;
try {
final Claims claims = getClaimsFromToken(token);
Long userId = Long.parseLong(String.valueOf(claims.get(CLAIM_KEY_USER_ID)));
String userName = String.valueOf(claims.get(CLAIM_KEY_USERNAME));
String account = claims.getSubject();
Object o = claims.get(CLAIM_KEY_AUTHORITIES);
List<String> authorities = (List<String>) claims.get(CLAIM_KEY_AUTHORITIES);
userDetail = new JwtUser(userId,userName,account,authorities );
} catch (Exception e) {
userDetail = null;
}
return userDetail;
}
public long getUserIdFromToken(String token) {
long userId = 0;
try {
final Claims claims = getClaimsFromToken(token);
userId = Long.parseLong(String.valueOf(claims.get(CLAIM_KEY_USER_ID)));
} catch (Exception e) {
log.error("获取用户id异常:{}",e);
}
return userId;
}
/**
* 获取用户账号
* @param token
* @return
*/
public String getUsernameFromToken(String token) {
String account =null;
try {
final Claims claims = getClaimsFromToken(token);
account = claims.getSubject();
} catch (Exception e) {
log.error("获取用户账号异常:{}",e);
}
return account;
}
/**
* 获取token创建时间
*
* @param token
* @return
*/
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = claims.getIssuedAt();
} catch (Exception e) {
created = null;
}
return created;
}
/**
* 生成token
* @param userDetail
*
* @return
*/
public String generateAccessToken(JwtUser userDetail) {
Map<String, Object> claims = generateClaims(userDetail);
claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()));
String accessToken = generateAccessToken(userDetail.getUsername(), claims);
//存储token
putToken(userDetail.getUsername(), accessToken);
return accessToken;
}
/**
* 获取过期时间
*
* @param token
* @return
*/
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
*
* @param token
* @return
*/
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
refreshedToken = generateAccessToken(claims.getSubject(), claims);
//存储token
putToken(claims.getSubject(), refreshedToken);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* 校验token是否失效
*
* @param token
* @return
*/
public Boolean validateToken(String token) {
return (!isTokenExpired(token));
}
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;
}
/**
* 获取Claims
*
* @param token
* @return
*/
public 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(JwtUser userDetail) {
Map<String, Object> claims = new HashMap<>(16);
claims.put(CLAIM_KEY_ACCOUNT, userDetail.getUsername());
claims.put(CLAIM_KEY_USERNAME, userDetail.getName());
claims.put(CLAIM_KEY_CREATED, new Date());
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 Set authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
Set<String> list = new HashSet<>();
for (GrantedAuthority ga : authorities) {
list.add(ga.getAuthority());
}
return list;
}
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();
}
}
六、service层
import com.gourd.common.annotation.TargetDataSource;
import com.gourd.common.data.DataSourceNames;
import com.gourd.common.exception.BadRequestException;
import com.gourd.common.rbac.vo.JwtUser;
import com.gourd.common.data.ResponseUserToken;
import com.gourd.common.rbac.dto.RbacUserDTO;
import com.gourd.common.rbac.entity.RbacUser;
import com.gourd.common.rbac.service.AuthService;
import com.gourd.common.rbac.service.RbacUserService;
import com.gourd.common.utils.JwtUtil;
import com.gourd.common.rbac.vo.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
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.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author: gourd
* createAt: 2018/9/17
*/
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
@RefreshScope
public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
private final JwtUtil jwtTokenUtil;
private final RbacUserService rbacUserService;
@Autowired
public AuthServiceImpl(AuthenticationManager authenticationManager, @Qualifier("userDetailsService") UserDetailsService userDetailsService,
JwtUtil jwtTokenUtil, RbacUserService rbacUserService ) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.rbacUserService = rbacUserService;
}
@Override
public UserVO register(RbacUserDTO rbacUserDTO) {
UserVO userDetail = new UserVO();
RbacUser rbacUser = rbacUserService.save(rbacUserDTO);
BeanUtils.copyProperties(rbacUser,userDetail);
return userDetail;
}
@Override
@TargetDataSource(DataSourceNames.SLAVE_DATASOURCE)
public ResponseUserToken login(String username, String password) {
//用户验证
final Authentication authentication = authenticate(username, password);
//存储认证信息
SecurityContextHolder.getContext().setAuthentication(authentication);
//生成token
final JwtUser userDetail = (JwtUser) authentication.getPrincipal();
final String token = jwtTokenUtil.generateAccessToken(userDetail);
return new ResponseUserToken(token, userDetail);
}
@Override
public void logout(String token) {
String userName = jwtTokenUtil.getUsernameFromToken(token);
jwtTokenUtil.deleteToken(userName);
}
@Override
public ResponseUserToken refresh(String token) {
String username = jwtTokenUtil.getUsernameFromToken(token);
JwtUser userDetail = (JwtUser) userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.canTokenBeRefreshed(token, userDetail.getLastPasswordResetDate())){
token = jwtTokenUtil.refreshToken(token);
return new ResponseUserToken(token, userDetail);
}
return null;
}
@Override
public JwtUser getUserByToken(String 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 BadRequestException("用户名或密码无效");
}
}
}
七、通用异常处理类:
import com.gourd.common.data.ApiError;
import com.gourd.common.data.BaseResponse;
import com.gourd.common.exception.BadRequestException;
import com.gourd.common.exception.EntityExistException;
import com.gourd.common.exception.EntityNotFoundException;
import com.gourd.common.utils.ThrowableUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.http.HttpStatus.*;
/**
* 异常处理器
* @author gourd
*/
@RestControllerAdvice
@Slf4j
public class BusinessExceptionHandler {
/**
* 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
System.out.println("请求有参数才进来 "+binder.getObjectName());
}
/**
* 把值绑定到Model中,使全局@RequestMapping可以获取到该值
* @param model
*/
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("author", "gourd");
}
/**
* 处理 接口无权访问异常AccessDeniedException
* @param e
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity handleAccessDeniedException(AccessDeniedException e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(FORBIDDEN.value(),e.getMessage());
return buildResponseEntity(apiError);
}
/**
* 处理自定义异常
* @param e
* @return
*/
@ExceptionHandler(value = BadRequestException.class)
public ResponseEntity<ApiError> badRequestException(BadRequestException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(e.getStatus(),e.getMessage());
return buildResponseEntity(apiError);
}
/**
* 处理 EntityExist
* @param e
* @return
*/
@ExceptionHandler(value = EntityExistException.class)
public ResponseEntity<ApiError> entityExistException(EntityExistException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(BAD_REQUEST.value(),e.getMessage());
return buildResponseEntity(apiError);
}
/**
* 处理 EntityNotFound
* @param e
* @return
*/
@ExceptionHandler(value = EntityNotFoundException.class)
public ResponseEntity<ApiError> entityNotFoundException(EntityNotFoundException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
ApiError apiError = new ApiError(NOT_FOUND.value(),e.getMessage());
return buildResponseEntity(apiError);
}
/**
* 处理所有不可知的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public Object handleException(Exception e, HttpServletRequest req){
BaseResponse response = new BaseResponse();
response.setCode(INTERNAL_SERVER_ERROR.value());
if(e.getMessage().length()>500){
response.setMsg("未知异常,请联系管理员");
}else {
response.setMsg(e.getMessage());
}
//使用HttpServletRequest中的header检测请求是否为ajax, 如果是ajax则返回json, 如果为非ajax则返回view(即ModelAndView)
String contentTypeHeader = req.getHeader("Content-Type");
String acceptHeader = req.getHeader("Accept");
String xRequestedWith = req.getHeader("X-Requested-With");
// 控制台打印log
e.printStackTrace();
if ((contentTypeHeader != null && contentTypeHeader.contains("application/json"))
|| (acceptHeader != null && acceptHeader.contains("application/json"))
|| "XMLHttpRequest".equalsIgnoreCase(xRequestedWith)) {
return response;
} else {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", e.getMessage());
modelAndView.addObject("url", req.getRequestURL());
modelAndView.addObject("stackTrace", e.getStackTrace());
modelAndView.setViewName("error");
return modelAndView;
}
}
/**
* 统一返回
* @param apiError
* @return
*/
private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
return new ResponseEntity(apiError, HttpStatus.valueOf(apiError.getStatus()));
}
}
八、controller层
1.注册登录登出等基本权限接口
package com.gourd.common.controller;
import com.gourd.common.data.BaseResponse;
import com.gourd.common.exception.BadRequestException;
import com.gourd.common.rbac.vo.JwtUser;
import com.gourd.common.data.ResponseUserToken;
import com.gourd.common.rbac.dto.RbacUserDTO;
import com.gourd.common.rbac.dto.RbacUserLoginDTO;
import com.gourd.common.rbac.service.AuthService;
import com.gourd.common.rbac.vo.UserVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
* @author gourd
* createAt: 2018/9/17
*/
@RestController
@Api(tags = "auth",description = "权限控制器")
@RequestMapping("/auth")
@RefreshScope
public class AuthController {
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
private AuthService authService;
@PostMapping(value = "/login")
@ApiOperation(value = "登陆", notes = "登陆成功返回token,测试管理员账号:admin,123456;用户账号:les123,admin")
public BaseResponse<ResponseUserToken> login(
@Valid @RequestBody RbacUserLoginDTO user){
ResponseUserToken response = authService.login(user.getAccount(), user.getPassword());
return BaseResponse.ok(response);
}
@GetMapping(value = "/logout" )
@ApiOperation(value = "登出", notes = "退出登陆")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public BaseResponse logout(HttpServletRequest request){
String token = request.getHeader(tokenHeader);
authService.logout(token);
return BaseResponse.ok("登出成功");
}
@GetMapping(value = "/user")
@ApiOperation(value = "根据token获取用户信息", notes = "根据token获取用户信息")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public BaseResponse<JwtUser> getUser(HttpServletRequest request){
String token = request.getHeader(tokenHeader);
JwtUser userDetail = authService.getUserByToken(token);
return BaseResponse.ok(userDetail);
}
@PostMapping(value = "/sign")
@ApiOperation(value = "用户注册")
public BaseResponse<UserVO> sign(@RequestBody RbacUserDTO user) {
return BaseResponse.ok(authService.register(user));
}
@GetMapping(value = "refresh")
@ApiOperation(value = "刷新token")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public BaseResponse<ResponseUserToken> refreshAndGetAuthenticationToken(
HttpServletRequest request){
String token = request.getHeader(tokenHeader);
ResponseUserToken response = authService.refresh(token);
if(response == null) {
throw new BadRequestException("token无效");
} else {
return BaseResponse.ok(response);
}
}
}
2、权限校验contoller:
通过注解 @PreAuthorize("hasAuthority('READ')") 、@PreAuthorize("hasRole('ROLE_ADMIN')") 标识接口权限
import com.baomidou.mybatisplus.plugins.Page;
import com.gourd.common.annotation.Log;
import com.gourd.common.rbac.dto.RbacUserDTO;
import com.gourd.common.rbac.dto.RbacUserSearchDTO;
import com.gourd.common.rbac.entity.RbacUser;
import com.gourd.common.rbac.service.RbacUserService;
import com.gourd.common.rbac.vo.JwtUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author gourd
*/
@RestController
@RequestMapping("/user")
@Api(tags = "user", description = "用户控制器")
public class UserController{
@Autowired
private RbacUserService userService;
@GetMapping("/current")
@ApiOperation(value = "获取当前用户")
@PreAuthorize("hasAuthority('READ')")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public JwtUser getCurrent() {
return userService.getCurrent();
}
@GetMapping("/all")
@ApiOperation(value = "获取所有的用户")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public List<RbacUser> findAll() {
return userService.findAll();
}
@GetMapping("/page")
@ApiOperation(value = "根据条件获取用户分页")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public Page<RbacUser> find(RbacUserSearchDTO rbacUserDTO) {
PageRequest pageRequest = new PageRequest(rbacUserDTO.getPageNo() - 1, rbacUserDTO.getPageSize());
Page<RbacUser> rbacUserPage = userService.find(rbacUserDTO, pageRequest);
return rbacUserPage;
}
@GetMapping("/{id}")
@ApiOperation(value = "根据id获取用户")
@PreAuthorize("hasAuthority('READ')")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public RbacUser getDetail(@PathVariable Long id) {
return userService.getById(id);
}
@GetMapping("/account-by")
@ApiOperation(value = "根据账号获取用户")
@PreAuthorize("hasAuthority('READ')")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public RbacUser getByAccount(@RequestParam String account) {
return userService.getByAccount(account);
}
@PostMapping("/add")
@ApiOperation(value = "创建用户")
@PreAuthorize("hasAuthority('WRITE')")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public RbacUser getByAccount(@RequestBody @Validated RbacUserDTO rbacUserDTO) {
return userService.save(rbacUserDTO);
}
@DeleteMapping("/{id}")
@ApiOperation(value = "删除用户")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Log("删除用户")
@ApiImplicitParams({@ApiImplicitParam(name = "jwt-token", value = "jwt-token", required = true, dataType = "string", paramType = "header")})
public void delete(@PathVariable Long id) {
userService.delete(id);
}
}
到此已经完成了搭建,上面主要贴出了核心代码,有可能会缺少文件报错。
有兴趣的小伙伴可以下载项目源码:https://blog.csdn.net/HXNLYW/article/details/98037354