记录学习Spring boot 整合Spring Security Jwt
学习参考 – 慢慢的干货
首先创建Spring Boot项目以及导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--hutool工具-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
application.yml配置
# 数据库相关配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/securitytext?serverTimezone=GMT&characterEncoding=utf-8
username: root
password: 1233
dbcp2:
test-on-borrow: true
validation-query: SELECT 1
hikari:
max-lifetime: 30000
servlet:
multipart:
max-file-size: 1024MB
max-request-size: 1024MB
# mybatis-plus相关配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# mapper-locations: classpath*:/mapper/**Mapper.xml
# 服务端口号
server:
port: 8030
# jwt配置
xiaojiang:
jwt:
secret: f4e2e52034348f86b67cde581c0f9eb5
expire: 604800
header: Authorization
创建数据库
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(64) DEFAULT NULL COMMENT '用户名',
`password` varchar(64) DEFAULT NULL COMMENT '用户密码',
`avatar` varchar(255) DEFAULT NULL COMMENT '用户头像',
`email` varchar(64) DEFAULT NULL COMMENT '用户邮箱',
`city` varchar(64) DEFAULT NULL COMMENT '城市',
`created_time` datetime DEFAULT NULL COMMENT '用户创建时间',
`updated_time` datetime DEFAULT NULL COMMENT '用户修改时间',
`last_login_time` datetime DEFAULT NULL COMMENT '用户上次登录时间',
`statu` int(5) NOT NULL COMMENT '用户状态 1可用|0不可用',
PRIMARY KEY (`id`),
UNIQUE KEY `UK_USERNAME` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'admin', '$2a$10$R7zegeWzOXPw871CmNuJ6upC0v8D373GuLuTw8jn6NET4BkPRZfgK', 'https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg', '123@qq.com', '广州', '2021-01-12 22:13:53', '2021-01-16 16:57:32', '2020-12-30 08:38:37', '1');
INSERT INTO `sys_user` VALUES ('2', 'test', '$2a$10$0ilP4ZD1kLugYwLCs4pmb.ZT9cFqzOZTNaMiHxrBnVIQUGUwEvBIO', 'https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg', 'test@qq.com', null, '2021-01-30 08:20:22', '2021-01-30 08:55:57', null, '1');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色id',
`name` varchar(64) NOT NULL COMMENT '角色名称',
`code` varchar(64) NOT NULL COMMENT '角色',
`created_time` datetime DEFAULT NULL COMMENT'角色创建时间',
`updated_time` datetime DEFAULT NULL COMMENT '角色修改时间',
`statu` int(5) NOT NULL COMMENT '角色状态 1可用|0不可用',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) USING BTREE,
UNIQUE KEY `code` (`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', '超级管理员', 'admin', '2021-01-16 13:29:03', '2021-01-17 15:50:45', '1');
INSERT INTO `sys_role` VALUES ('2', '普通用户', 'normal', '2021-01-04 10:09:14', '2021-01-30 08:19:52', '1');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`role_id` bigint(20) NOT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '1', '2');
INSERT INTO `sys_user_role` VALUES ('3', '2', '2');
补上两张菜单表
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`parent_id` int(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(64) NOT NULL COMMENT '菜单名称',
`path` varchar(255) DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(255) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`component` varchar(255) DEFAULT NULL,
`type` int(5) NOT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(32) DEFAULT NULL COMMENT '菜单图标',
`created_time` datetime NOT NULL COMMENT '菜单创建时间',
`updated_time` datetime DEFAULT NULL COMMENT '菜单修改时间',
`statu` int(5) NOT NULL COMMENT '状态1可用|0不可用',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES ('1', '0', '系统管理', '', 'sys:manage', '', '0', 'el-icon-s-operation', '2021-01-15 18:58:18', '2021-01-15 18:58:20', '1');
INSERT INTO `sys_menu` VALUES ('2', '1', '用户管理', '/sys/users', 'sys:user:list', 'sys/User', '1', 'el-icon-s-custom', '2021-01-15 19:03:45', '2021-01-15 19:03:48', '1');
INSERT INTO `sys_menu` VALUES ('3', '1', '角色管理', '/sys/roles', 'sys:role:list', 'sys/Role', '1', 'el-icon-rank', '2021-01-15 19:03:45', '2021-01-15 19:03:48', '1');
INSERT INTO `sys_menu` VALUES ('4', '1', '菜单管理', '/sys/menus', 'sys:menu:list', 'sys/Menu', '1', 'el-icon-menu', '2021-01-15 19:03:45', '2021-01-15 19:03:48', '1');
INSERT INTO `sys_menu` VALUES ('5', '0', '系统工具', '', 'sys:tools', null, '0', 'el-icon-s-tools', '2021-01-15 19:06:11', null, '1');
INSERT INTO `sys_menu` VALUES ('6', '5', '数字字典', '/sys/dicts', 'sys:dict:list', 'sys/Dict', '1', 'el-icon-s-order', '2021-01-15 19:07:18', '2021-01-18 16:32:13', '1');
INSERT INTO `sys_menu` VALUES ('7', '3', '添加角色', '', 'sys:role:save', '', '2', '', '2021-01-15 23:02:25', '2021-01-17 21:53:14', '0');
INSERT INTO `sys_menu` VALUES ('8', '2', '添加用户', null, 'sys:user:save', null, '2', null, '2021-01-17 21:48:32', null, '1');
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`role_id` int(20) NOT NULL,
`menu_id` int(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES ('1', '1', '1');
INSERT INTO `sys_role_menu` VALUES ('2', '1', '2');
INSERT INTO `sys_role_menu` VALUES ('3', '1', '3');
INSERT INTO `sys_role_menu` VALUES ('4', '1', '4');
INSERT INTO `sys_role_menu` VALUES ('5', '1', '5');
INSERT INTO `sys_role_menu` VALUES ('6', '1', '6');
INSERT INTO `sys_role_menu` VALUES ('7', '1', '7');
INSERT INTO `sys_role_menu` VALUES ('8', '1', '8');
INSERT INTO `sys_role_menu` VALUES ('9', '2', '1');
INSERT INTO `sys_role_menu` VALUES ('10', '2', '2');
INSERT INTO `sys_role_menu` VALUES ('11', '2', '3');
INSERT INTO `sys_role_menu` VALUES ('12', '2', '4');
INSERT INTO `sys_role_menu` VALUES ('13', '2', '5');
使用mybatis-plus代码生成器
public class CodeGenerator {
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String input= scanner.next();
if (StringUtils.isNotEmpty(input)) {
return input;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("Mr.jiang");
gc.setOpen(false);
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setUrl("jdbc:mysql://localhost:3306/securitytext?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("1233");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(null);
pc.setParent("com.jsp");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("sys_");
mpg.setStrategy(strategy);
mpg.execute();
}
}
创建mybatis-plus配置类
@Configuration
public class MybatisPlusConfig {
// 分页配置
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
@Component
public class MyMetaObjectConfig implements MetaObjectHandler {
// 自动插入、更新
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
这里是为了entiy类中的字段
/**
* 用户创建时间
*/
@TableField(fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "GMT+8")
private LocalDateTime createdTime;
/**
* 用户修改时间
*/
@TableField(fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "GMT+8")
private LocalDateTime updatedTime;
/**
* 用户上次登录时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "GMT+8")
private LocalDateTime lastLoginTime;
Result结果集
@Data
public class Result implements Serializable {
private int code;
private String message;
private Object data;
Result(int code,String message,Object data) {
this.code = code;
this.message = message;
this.data = data;
}
Result(int code,String message) {
this.code = code;
this.message = message;
}
}
public enum ResultCode {
//成功
SUCCESS(200),
//失败
FAIL(400),
//用户未认证
UNAUTHORIZED(401),
//url找不到
NOT_FOUND(404),
//访问受限
FORBIDDEN(403),
//服务器错误
INTERNAL_SERVER_ERROR(500);
public int code;
ResultCode(int code) {
this.code = code;
}
}
public class ResultFactory {
//成功
public static Result successful(Object data) {
return buildResult(ResultCode.SUCCESS, "成功", data);
}
//失败
public static Result fail(String message) {
return buildResult(ResultCode.FAIL, message, null);
}
public static Result buildResult(int ResultCode, String message, Object data) {
return new Result(ResultCode, message, data);
}
public static Result buildResult(ResultCode ResultCode, String message, Object data) {
return new Result(ResultCode.code, message, data);
}
public static Result buildResult(ResultCode ResultCode, String message) {
return new Result(ResultCode.code, message);
}
}
** 重点 Spring Security 配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginFailureHandler loginFailureHandler;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Autowired
private JwtLogoutSuccessHandler jwtLogoutSuccessHandler;
@Bean
JWTAuthenticationFilter jwtAuthenticationFilter() throws Exception {
return new JWTAuthenticationFilter(authenticationManager());
}
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//白名单
public static final String[] URL_WHITELIST = {
"/favicon.ico",
"/login",
"/logout",
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
//关闭session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
//退出
.and()
.logout()
.logoutSuccessHandler(jwtLogoutSuccessHandler)
//资源访问控制
.and()
.authorizeRequests()
//白名单
.antMatchers(URL_WHITELIST).permitAll()
//其他请求需要认证
.anyRequest().authenticated()
//异常处理器
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
.addFilter(jwtAuthenticationFilter());
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
重写UserDetails
public class MyUserDetails implements UserDetails {
private Integer id;
private String password;
private final String username;
private final Collection<? extends GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
public MyUserDetails(Integer id, String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(id, username, password, true, true, true, true, authorities);
}
public MyUserDetails(Integer id, String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
Assert.isTrue(username != null && !"".equals(username) && password != null,
"Cannot pass null or empty values to constructor");
this.id = id;
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}
重写UserDetailsService
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询数据库判断username是否存在,如果不存在则抛出异常UsernameNotFoundException
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
User user = userMapper.selectOne(wrapper);
if (user == null) {
throw new UsernameNotFoundException("用户名或密码不正确!");
}
//从数据库查出的用户信息id,username,password,authorityList
List<GrantedAuthority> authorityList = getUserAuthority(user.getId());
return new MyUserDetails(user.getId(), user.getUsername(), user.getPassword(), authorityList);
}
public List<GrantedAuthority> getUserAuthority(Integer id) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(
userService.getUserAuthorityInfo(id)
);
}
}
public interface UserRoleMapper extends BaseMapper<UserRole> {
@Select("SELECT code, name FROM sys_user_role JOIN sys_role ON sys_user_role.role_id = sys_role.id WHERE user_id = #{uid};")
List<Role> selectRoles(Integer uid);
}
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT rm.menu_id FROM sys_user_role ur LEFT JOIN sys_role_menu rm ON ur.role_id = rm.role_id WHERE ur.user_id = #{uid};")
List<Integer> getNavMenuIds(Integer uid);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserRoleMapper userRoleMapper;
@Autowired
private UserMapper userMapper;
//根据用户id获取用户角色
@Override
public String getUserAuthorityInfo(Integer id) {
List<Role> roles = userRoleMapper.selectRoles(id);
List<Integer> menuIds = userMapper.getNavMenuIds(id);
List<Menu> menus = menuMapper.selectBatchIds(menuIds);
String roleNames = roles.stream().map(role -> "ROLE_" + role.getCode()).collect(Collectors.joining(","));
String permNames = menus.stream().map(menu -> menu.getPerms()).collect(Collectors.joining(","));
// ROLE_admin,ROLE_normal,sys:user:list...,
String authority = roleNames.concat(",").concat(permNames);
return authority;
}
@Override
public User getByUsername(String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
User user = userMapper.selectOne(wrapper);
return user;
}
}
LoginSuccessHandler
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private JwtUtil jwtUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
// 生成jwt返回
String token = jwtUtil.generateToken(authentication.getName());
response.setHeader(jwtUtil.getHeader(), token);
Result result = ResultFactory.successful("");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
LoginFailureHandler
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
Result result = ResultFactory.fail("Bad credentials".equals(exception.getMessage()) ? "用户名或密码不正确" : exception.getMessage());
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
JwtLogoutSuccessHandler
@Component
public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private JwtUtil jwtUtil;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (authentication != null) {
new SecurityContextLogoutHandler().logout(request, response, authentication);
}
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
response.setHeader(jwtUtil.getHeader(), "");
Result result = ResultFactory.successful("");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
JwtAuthenticationEntryPoint
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ServletOutputStream outputStream = response.getOutputStream();
Result result = ResultFactory.buildResult(ResultCode.UNAUTHORIZED, "请先登录!");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
JwtAccessDeniedHandler
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
ServletOutputStream outputStream = response.getOutputStream();
//Result result = ResultFactory.fail(e.getMessage());
Result result = ResultFactory.buildResult(ResultCode.FORBIDDEN,"权限不足");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
jwt相关配置
JwtUtil
@Data
@Component
@ConfigurationProperties(prefix = "xiaojiang.jwt")
public class JwtUtil {
private long expire;
private String secret;
private String header;
//生成JWT
public String generateToken(String username) {
Date exireDate = new Date(new Date().getTime() + 1000 * expire);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(exireDate)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
//解析JWT
public Claims getClaimByToken(String jwt) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
return null;
}
}
//JWT是否过期
public boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
}
JWTAuthenticationFilter
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsServiceImpl userDetailsService;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwt = request.getHeader(jwtUtil.getHeader());
if (StrUtil.isBlankOrUndefined(jwt)) {
chain.doFilter(request, response);
return;
}
Claims claim = jwtUtil.getClaimByToken(jwt);
if (claim == null) {
throw new JwtException("token异常");
}
if (jwtUtil.isTokenExpired(claim)) {
throw new JwtException("token已过期");
}
String username = claim.getSubject();
//获取用户的权限等信息
User user = userService.getByUsername(username);
List<GrantedAuthority> userAuthority = userDetailsService.getUserAuthority(user.getId());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userAuthority);
System.out.println("token->" + token);
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
}
}
ok大功告成 使用postman试试效果
admin用户拥有admin和normal角色,test用户拥有normal角色
在Controller中分配接口权限
// 如果使用 @PreAuthorize("hasRole('ROLE_ADMIN')")需要在类的头部加上@EnableGlobalMethodSecurity(prePostEnabled = true) //打开权限验证
@RestController
@RequestMapping("/user")
@EnableGlobalMethodSecurity(prePostEnabled = true) //打开权限验证
public class UserController {
@Autowired
private UserService userService;
@PreAuthorize("hasRole('admin')")
@GetMapping("/admin")
public String hello() {
return "hello admin";
}
@PreAuthorize("hasRole('normal')")
@GetMapping("/test")
public String test() {
return "hello test";
}
@PreAuthorize("hasRole('admin')")
@GetMapping("/list")
public Result list() {
return ResultFactory.successful(userService.list());
}
}
首先使用admin用户登录
在Header中有Authorization,也就是我们需要的token
接着使用admin用户的token去访问其他接口
admin用户都可以访问
接着使用test用户登录