spring security+jwt 实现认证授权

目录结构

spring security+jwt 实现认证授权_第1张图片spring security+jwt 实现认证授权_第2张图片

具体代码

config 配置类

DefaultControllerAdvice.java

package com.stu.manage.demo.config;

import com.stu.manage.demo.result.Result;
import com.stu.manage.demo.result.ResultEnum;
import com.stu.manage.demo.result.ResultUtil;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 捕获Control层抛出的异常 做统一通知处理
 */

/*@ControllerAdvice
@ResponseBody
public class DefaultControllerAdvice {

    @ExceptionHandler(Exception.class)
    public Result exceptionHandler() {
        return ResultUtil.error(ResultEnum.UNKNOWN_ERROR.getCode(),ResultEnum.UNKNOWN_ERROR.getMsg());
    }
}*/

MybatisPlusConfig.java

package com.stu.manage.demo.config;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
// 配置类
// 如果这里使用 @MapperScan 则 在启动类 SpingbootTestApplication@MapperScan可以不用写
//@MapperScan("com.lomonkey.mapper")
@Configuration
@EnableTransactionManagement // 开启注解事务管理,等同于xml配置文件中的 
public class MybatisPlusConfig implements TransactionManagementConfigurer {
    @Resource(name="txManager1")
    private PlatformTransactionManager txManager1;
    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false
     * 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        paginationInnerInterceptor.setOverflow(true);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(50L);
        // 开启 count 的 join 优化,只针对部分 left join
        //paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
//        paginationInnerInterceptor.setDialect();
        // 添加分页拦截器插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        // 添加乐观锁配置
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
    /*// 注册乐观锁插件
    @Bean
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }*/
    // 创建事务管理器1  其中DataSource会自动注入
    //在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
    // 关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
    // 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
    // 如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。
    @Bean(name = "txManager1")
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    // 创建事务管理器2
    /*@Bean(name = "txManager2")
    public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }*/
    // 实现接口 TransactionManagementConfigurer 方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return txManager1;
    }
}

MyMetaObjectHandler

package com.stu.manage.demo.config;
import java.util.Date;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component //一定不要忘记吧处理器加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时候的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        //设置字段的值(String fieldName字段名,Object fieldVal要传递的值,MetaObject metaObject)
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.strictInsertFill(metaObject, "modifyTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)
    }
    //更新时间的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "modifyTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)
    }
}

SecurityConfiguration.java

package com.stu.manage.demo.config;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpMethod;
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.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import com.stu.manage.demo.config.service.UserDetailsServiceImpl;
import com.stu.manage.demo.constants.SecurityConstants;
import com.stu.manage.demo.exception.JwtAccessDeniedHandler;
import com.stu.manage.demo.exception.JwtAuthenticationEntryPoint;
import com.stu.manage.demo.filter.JWTAuthenticationFilter;
import com.stu.manage.demo.filter.JwtAuthorizationFilter;
import static java.util.Collections.singletonList;
import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private final StringRedisTemplate stringRedisTemplate;
    @Autowired
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    public SecurityConfiguration(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    /**
     * 密码编码器
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 设置自定义的userDetailsService以及密码编码器
        auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
    }
    /*@Bean
    public JwtAuthorizationFilter jwtAuthorizationFilter() throws Exception {
        JwtAuthorizationFilter filter = new JwtAuthorizationFilter(authenticationManager(), stringRedisTemplate);
        return filter;
    }*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors(withDefaults())
                .csrf().disable() // 禁用 CSRF 禁用自带的跨域策略
                .authorizeRequests()
                // 指定的接口直接放行
                .antMatchers(SecurityConstants.SWAGGER_WHITELIST).permitAll() // swagger
                //.antMatchers(HttpMethod.GET, SecurityConstants.SYSTEM).permitAll()
                .antMatchers(HttpMethod.POST, SecurityConstants.AUTH_LOGIN_URL).permitAll()
                //.antMatchers(SecurityConstants.HAS_AUTH).hasAuthority("SystemUserUpdate")
                //.antMatchers("student/insert/*").hasAuthority("SystemUserInsert")
                .anyRequest().authenticated() // 其他的接口都需要认证后才能请求
                .and()
                //添加自定义Filter
                .addFilter(new JWTAuthenticationFilter(authenticationManager(), stringRedisTemplate))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), stringRedisTemplate))
                // 不需要session(不创建会话)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 授权异常处理
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler);
        // 防止H2 web 页面的Frame 被拦截
        http.headers().frameOptions().disable();
        /*http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .and()
                .logout()
                .and()
                .httpBasic();*/
    }
    /**
     * Cors配置优化
     **/
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(singletonList("*"));
        // configuration.setAllowedOriginPatterns(singletonList("*"));
        configuration.setAllowedHeaders(singletonList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE", "PUT", "OPTIONS"));
        configuration.setExposedHeaders(singletonList(SecurityConstants.TOKEN_HEADER));
        configuration.setAllowCredentials(false);
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

SwaggerConfig

package com.stu.manage.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket docket(){
        return  new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.stu.manage.demo.controller")).build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("学生管理系统API")
                .description("接口地址:https://")
                .version("1.0").build();
    }
}

constants 

 SecurityConstants

package com.stu.manage.demo.constants;

public final class SecurityConstants {
    /**
     * 角色的key
     **/
    public static final String ROLE_CLAIMS = "rol";

    /**
     * rememberMe 为 false 的时候过期时间是1个小时
     */
    public static final long EXPIRATION = 60 * 60L;

    /**
     * rememberMe 为 true 的时候过期时间是7天
     */
    public static final long EXPIRATION_REMEMBER = 60 * 60 * 24 * 7L;

    /**
     * JWT签名密钥硬编码到应用程序代码中,应该存放在环境变量或.properties文件中。
     */
    public static final String JWT_SECRET_KEY = "C*F-JaNdRgUkXn2r5u8x/A?D(G+KbPeShVmYq3s6v9y$B&E)H@McQfTjWnZr4u7w";
    // JWT token defaults
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String TOKEN_TYPE = "JWT";
    // Swagger WHITELIST
    public static final String[] SWAGGER_WHITELIST = {
            "/swagger-ui.html",
            "/swagger-ui/*",
            "/swagger-resources/**",
            "/v2/api-docs",
            "/v3/api-docs",
            "/webjars/**",
            //knife4j
            "/doc.html",
    };
    public static final String[] SYSTEM = {
            "/login"
    };
    // System WHITELIST
    public static final String AUTH_LOGIN_URL = "/auth/login";
    public static final String[] HAS_AUTH = {
            "/student/update/*"
    };
    private SecurityConstants() {
    }
}

controller 

 LoginController

package com.stu.manage.demo.controller;
import com.stu.manage.demo.dto.LoginRequest;
import com.stu.manage.demo.entity.LoginToken;
import com.stu.manage.demo.result.Result;
import com.stu.manage.demo.result.ResultEnum;
import com.stu.manage.demo.result.ResultUtil;
import com.stu.manage.demo.service.IUserService;
import com.stu.manage.demo.service.LoginService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
@Slf4j
public class LoginController {
    @Autowired
    private LoginService loginService;

    /**
     * 登录验证: 
     * 方式一:直接调用该接口,在loginService.createToken(loginRequest)中进行了验证并生成token
     * 方式二:自定义认证拦截器JWTAuthenticationFilter
     */
    @PostMapping("/login-up")
    public Result login(@RequestBody LoginRequest loginRequest) {
        String token = null;
        try {
            token = loginService.createToken(loginRequest);
        } catch (Exception e) {
            log.error(e.getMessage());
            return ResultUtil.error(ResultEnum.VERIFY_FAIL.getCode(), ResultEnum.VERIFY_FAIL.getMsg());
        }
        LoginToken loginToken = new LoginToken();
        loginToken.setName(loginRequest.getUsername());
        loginToken.setToken(token);
        /*HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set(SecurityConstants.TOKEN_HEADER, token);*/
        return ResultUtil.success(loginToken);
       // return new ResponseEntity<>(httpHeaders, HttpStatus.OK);
    }
}
StudentController
package com.stu.manage.demo.controller;
import com.stu.manage.demo.result.Result;
import com.stu.manage.demo.result.ResultEnum;
import com.stu.manage.demo.result.ResultUtil;
import com.stu.manage.demo.service.StudentService;
import com.stu.manage.demo.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
    @Autowired
    private StudentService studentService;
    @GetMapping("/getAll")
//    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    @PreAuthorize("hasAuthority('SystemContent')")
    public Result getAllStudents(){
        List res = studentService.getAllStudents();
        if(res!=null) {
            return ResultUtil.success(res);
        }else {
            return ResultUtil.error(ResultEnum.DATA_IS_NULL.getCode(),ResultEnum.DATA_IS_NULL.getMsg());
        }
    }
    @GetMapping("/find/id/{id}")
    public Result studentById(@PathVariable("id")Integer id){
        List res=studentService.getStudentById(id);
        if(!res.isEmpty()){
            return ResultUtil.success(res);
        }else {
            return ResultUtil.error(ResultEnum.STUDENT_NOT_EXIST.getCode(),ResultEnum.STUDENT_NOT_EXIST.getMsg());
        }

    }
    @PostMapping ("/insert")
    public Result insert(@RequestBody @Valid Student student){
        int res=studentService.insertStudent(student);
        if(res==1){
            return ResultUtil.success(res);
        }else {
            return ResultUtil.error(ResultEnum.UNKNOWN_ERROR.getCode(),ResultEnum.UNKNOWN_ERROR.getMsg());
        }
    }
    @PostMapping("/update")
    //@PreAuthorize("hasAnyRole('ROLE_ROOT')")
    @PreAuthorize("hasAuthority('SystemUserUpdate')")
    public Result update(@RequestBody @Valid Student student){
         int res=studentService.updateStudent(student);
         if(res>0){
             return ResultUtil.success();
         }else {
             return ResultUtil.error(ResultEnum.UPDATE_FAIL.getCode(),ResultEnum.UPDATE_FAIL.getMsg());
         }
    }
}

LoginRequest

package com.stu.manage.demo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author cdc
 * @description 用户登录请求DTO
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginRequest {
    private String username;
    private String password;
    private Boolean rememberMe;
}

entity 

 JwtUser

package com.stu.manage.demo.entity;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class JwtUser implements UserDetails {
    private Long id;
    private String username;
    private String password;
    private Boolean enabled;
    private Collection authorities;
    public JwtUser(TbUser tbUser) {
        this.id = tbUser.getId();
        this.username = tbUser.getUserName();
        this.password = tbUser.getPassWord();
        this.enabled = tbUser.getEnabled() == null ? true : tbUser.getEnabled();;
    }
    public void setAuthorities(List list) {
        this.authorities = list;
    }
    public JwtUser() {}
    @Override
    public Collection getAuthorities() {
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
    public Long getId() {
        return id;
    }
}

 LoginToken

package com.stu.manage.demo.entity;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Data
@ToString
public class LoginToken {
    @NotNull
    private String name;
    private String token;
}

Student 

 

package com.stu.manage.demo.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@Builder
public class Student {
    /**
     *  配置主键生成策略
     */
    @TableId(value = "student_id", type = IdType.AUTO)
    private Integer studentId;
    @TableField("student_name")
    @NotBlank(message = "姓名不能为空")
    private String studentName;
    @TableField("id_card")
    @NotBlank(message = "身份证不能为空")
    private String idCard;
    @NotNull
    private String sex;
    @TableField("grade_id")
    @NotNull
    private Integer gradeId;
    @TableField("class_id")
    @NotNull
    private Integer classId;
    @TableField(value = "createtime", fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(value = "modifytime", fill = FieldFill.INSERT_UPDATE)
    private Date modifyTime;
    /**
     * 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
     * 整数类型下 newVersion = oldVersion + 1
     * newVersion 会回写到 entity 中
     * 仅支持 updateById(id) 与 update(entity, wrapper) 方法
     * 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
     */
    @Version //乐观锁Version注解
    private Integer version;
}
TbAuditBase
package com.stu.manage.demo.entity;
import java.util.Date;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
public class TbAuditBase {
    @TableField(value = "created", fill = FieldFill.INSERT)
    @NotNull
    private Date createTime;
    @TableField(value = "updated", fill = FieldFill.UPDATE)
    @NotNull
    private Date modifyTime;
}

TbPermission

package com.stu.manage.demo.entity;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TbPermission extends TbAuditBase {
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("parent_id")
    @NotNull
    private Long parentId;
    @NotNull
    private String name;
    @NotNull
    private String enname;
    @NotNull
    private String url;
    private String description;
}

TbRole

package com.stu.manage.demo.entity;
import java.util.List;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@TableName(value = "tb_role", resultMap = "BaseResultMap")
@AllArgsConstructor
@NoArgsConstructor
public class TbRole extends TbAuditBase {
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("parent_id")
    @NotNull
    private Long parentId;
    @NotNull
    private String name;
    @NotNull
    private String enname;
    private String description;
    // 一对多
    private List rolePermissions;
}
TbRolePermission
package com.stu.manage.demo.entity;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TbRolePermission {
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("permission_id")
    @NotNull
    private Long permissionId;
    @TableField("role_id")
    @NotNull
    private Long roleId;
}

 TbUser

package com.stu.manage.demo.entity;
import java.util.List;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@TableName(value = "tb_user", resultMap = "BaseResultMap")
@AllArgsConstructor
@NoArgsConstructor
public class TbUser extends TbAuditBase {
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("username")
    @NotNull
    private String userName;
    @TableField("password")
    @NotNull
    private String passWord;
    @NotNull
    private String phone;
    private String email;
    private Boolean enabled;
    /**
     * 用户表和用户权限中间表是一对多的关系
     */
    private List userRoles;
}
TbUserRole
package com.stu.manage.demo.entity;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TbUserRole {
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("user_id")
    @NotNull
    private Long userId;
    @TableField("role_id")
    @NotNull
    private Long roleId;
}

exception 

 JwtAccessDeniedHandler

package com.stu.manage.demo.exception;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
/**
 * @author cdc
 * @description AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
 */
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    /**
     * 当用户尝试访问需要权限才能的REST资源而权限不足的时候,
     * 将调用此方法发送403响应以及错误信息
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        accessDeniedException = new AccessDeniedException("无权限操作!!");
        response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
        /*response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ServletOutputStream outputStream = response.getOutputStream();
        Result result = ResultUtil.error(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
        outputStream.write(JSONUtils.toJSONString(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();*/
    }
}

JwtAuthenticationEntryPoint

package com.stu.manage.demo.exception;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
/**
 * @author cdc
 * @description AuthenticationEntryPoint 用来解决匿名用户访问需要权限才能访问的资源时的异常
 */
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    /**
     * 当用户尝试访问需要权限才能的REST资源而不提供Token或者Token错误或者过期时,
     * 将调用此方法发送401响应以及错误信息
     */
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }
}

filter 

 JWTAuthenticationFilter

 

package com.stu.manage.demo.filter;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stu.manage.demo.constants.SecurityConstants;
import com.stu.manage.demo.dto.LoginRequest;
import com.stu.manage.demo.entity.JwtUser;
import com.stu.manage.demo.utils.JwtTokenUtils;
// 认证过滤器
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final StringRedisTemplate stringRedisTemplate;
    private ThreadLocal rememberMe = new ThreadLocal<>();
    private AuthenticationManager authenticationManager;
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate) {
        this.authenticationManager = authenticationManager;
        this.stringRedisTemplate = stringRedisTemplate;
        // 设置登录请求的 URL
        super.setFilterProcessesUrl(SecurityConstants.AUTH_LOGIN_URL);
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            // 从输入流中获取到登录的信息
            LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class);
            rememberMe.set(loginRequest.getRememberMe());
            // 这部分和attemptAuthentication方法中的源码是一样的,
            // 只不过由于这个方法源码的是把用户名和密码这些参数的名字是死的,所以我们重写了一下
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(), loginRequest.getPassword());
            return authenticationManager.authenticate(authRequest);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 如果验证成功,就生成token并返回
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authentication) {
        JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
        List roles = jwtUser.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
        // 创建 Token
        String token = JwtTokenUtils.createToken(jwtUser.getUsername(), jwtUser.getId().toString(), roles, rememberMe.get());
        stringRedisTemplate.opsForValue().set(jwtUser.getId().toString(), token);
        // Http Response Header 中返回 Token
        response.setHeader(SecurityConstants.TOKEN_HEADER, token);
    }
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
    }
}

JwtAuthorizationFilter 

 

package com.stu.manage.demo.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.stu.manage.demo.constants.SecurityConstants;
import com.stu.manage.demo.utils.JwtTokenUtils;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
/**
 * 授权过滤器
 * @description 过滤器处理所有HTTP请求,并检查是否存在带有正确令牌的Authorization标头。例如,如果令牌未过期或签名密钥正确。
 */
@Slf4j
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
    private final StringRedisTemplate stringRedisTemplate;
    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate) {
        super(authenticationManager);
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String token = request.getHeader(SecurityConstants.TOKEN_HEADER);
        if (token == null || !token.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            SecurityContextHolder.clearContext();
            chain.doFilter(request, response);
            return;
        }
        String tokenValue = token.replace(SecurityConstants.TOKEN_PREFIX, "");
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            String previousToken = stringRedisTemplate.opsForValue().get(JwtTokenUtils.getId(tokenValue));
            if (!token.equals(previousToken)) {
                SecurityContextHolder.clearContext();
                chain.doFilter(request, response);
                return;
            }
            authentication = JwtTokenUtils.getAuthentication(tokenValue);
        } catch (JwtException e) {
            logger.error("Invalid jwt : " + e.getMessage());
        }
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }
}

mapper 

 TbPermissionMapper

package com.stu.manage.demo.mapper;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.stu.manage.demo.entity.TbPermission;
import com.stu.manage.demo.entity.TbUser;
@Repository
public interface TbPermissionMapper extends BaseMapper {
    List findByUser(TbUser tbUser);
}

TbPermissionMapper.xml 




  
    
    
    
    
    
    
    
    
  
  
    id, parent_id, `name`, enname, url, description, created, updated
  
  

 TbUserMapper.xml




  
    
    
    
    
    
    
    
    
    
  
  
  
    id, username, `password`, phone, email, created, updated
  

 result

Result

package com.stu.manage.demo.result;
import lombok.Data;
@Data
public class Result {
    private Integer code;
    private String msg;
    private T data;
    public Result() {
        super();
    }
    public Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

ResultEnum 

package com.stu.manage.demo.result;
public enum ResultEnum {
    //可自行定义,与前端交互
    UNKNOWN_ERROR(-1,"未知错误"),
    SUCCESS(200,"成功"),
    STUDENT_NOT_EXIST(1,"学生不存在"),
    STUDENT_IS_EXISTS(2,"学生已存在"),
    DATA_IS_NULL(3,"数据为空"),
    DELETE_FAIL(5,"删除失败"),
    UPDATE_FAIL(6,"更新失败"),
    VERIFY_FAIL(7,"验证失败")
    ;
    private Integer code;
    private String msg;
    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }
    public String getMsg() {
        return msg;
    }
}

ResultUtil

package com.stu.manage.demo.result;
public class ResultUtil {
    /**成功且带数据**/
    public static Result success(Object object){
        Result result = new Result();
        result.setCode(ResultEnum.SUCCESS.getCode());
        result.setMsg(ResultEnum.SUCCESS.getMsg());
        result.setData(object);
        return result;
    }
    /**成功但不带数据**/
    public static Result success(){
        return success(null);
    }
    /**失败**/
    public static Result error(Integer code,String msg){
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

service 

 LoginServiceImpl

package com.stu.manage.demo.service.impl;
import java.util.List;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stu.manage.demo.dto.LoginRequest;
import com.stu.manage.demo.entity.TbPermission;
import com.stu.manage.demo.entity.TbUser;
import com.stu.manage.demo.mapper.LoginMapper;
import com.stu.manage.demo.mapper.TbUserMapper;
import com.stu.manage.demo.service.ITbPermissionService;
import com.stu.manage.demo.service.ITbUserService;
import com.stu.manage.demo.service.LoginService;
import com.stu.manage.demo.utils.JwtTokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {
    private static final String TAG = LoginServiceImpl.class.getName();
//    private static final Logger logger = Logger.getLogger(LoginServiceImpl.class.getName());
    @Autowired
    private LoginMapper loginMapper;
    @Autowired
    private ITbUserService tbUserService;
    @Autowired
    private ITbPermissionService permissionService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public String getAdmin(String name){
        return loginMapper.getAdmin(name);
    }
    @Override
    public String createToken(LoginRequest loginRequest) {
        TbUserMapper mapper = tbUserService.getMapper();
        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userName", loginRequest.getUsername());
        TbUser tbUser = mapper.selectOne(queryWrapper);
        if (!tbUserService.check(loginRequest.getPassword(), tbUser.getPassWord())) {
            throw new BadCredentialsException("The username or password is not correct");
        }
        List permissions = permissionService.findByUser(tbUser).stream().map(TbPermission::getEnname).collect(Collectors.toList());
        String token = JwtTokenUtils.createToken(tbUser.getUserName(), tbUser.getId().toString(), permissions, true);
        log.info(TAG, "{} createToken is [}", tbUser.getUserName(), token);
        //logger.info("{} createToken is [}", tbUser.getUserName(), token);
        stringRedisTemplate.opsForValue().set(tbUser.getId().toString(), token);
        return token;
    }
}

utils 

CurrentUserUtils

package com.stu.manage.demo.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import com.stu.manage.demo.entity.TbUser;
import com.stu.manage.demo.service.ITbUserService;
import lombok.RequiredArgsConstructor;
/**
 * @description 获取当前请求的用户
 */
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CurrentUserUtils {
    private final ITbUserService tbUserService;
    public TbUser getCurrentUser() {
        return tbUserService.findByName(getCurrentUserName());
    }
    private String getCurrentUserName() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() != null) {
            return (String) authentication.getPrincipal();
        }
        return null;
    }
}

JwtTokenUtils 

package com.stu.manage.demo.utils;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.SecretKey;
import javax.xml.bind.DatatypeConverter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import com.stu.manage.demo.constants.SecurityConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

public class JwtTokenUtils {
    /**
     * 生成足够的安全随机密钥,以适合符合规范的签名
     */
    private static final byte[] API_KEY_SECRET_BYTES = DatatypeConverter.parseBase64Binary(SecurityConstants.JWT_SECRET_KEY);
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(API_KEY_SECRET_BYTES);
    public static String createToken(String username, String id, List permissions, boolean isRememberMe) {
        long expiration = isRememberMe ? SecurityConstants.EXPIRATION_REMEMBER : SecurityConstants.EXPIRATION;
        final Date createdDate = new Date();
        final Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);
        String tokenPrefix = Jwts.builder()
                .setHeaderParam("type", SecurityConstants.TOKEN_TYPE)
                .signWith(SECRET_KEY, SignatureAlgorithm.HS256) // 密钥
                .claim(SecurityConstants.ROLE_CLAIMS, String.join(",", permissions)) // 载荷
                .setId(id) // jwt唯一标识
                .setIssuer("SnailClimb") //jwt的签发者
                .setIssuedAt(createdDate) //jwt的签发标识
                .setSubject(username) //sub(Subject):代表这个JWT的主体,是一个json格式的字符串,作为什么用户的唯一标志
                .setExpiration(expirationDate) // 过期时间
                .compact(); //压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
        return SecurityConstants.TOKEN_PREFIX + tokenPrefix; // 添加 token 前缀 "Bearer ";
    }
    public static String getId(String token) {
        Claims claims = getClaims(token);
        return claims.getId();
    }
    public static UsernamePasswordAuthenticationToken getAuthentication(String token) {
        Claims claims = getClaims(token);
        List authorities = getAuthorities(claims);
        String userName = claims.getSubject();
        return new UsernamePasswordAuthenticationToken(userName, token, authorities);
    }
    private static List getAuthorities(Claims claims) {
        String role = (String) claims.get(SecurityConstants.ROLE_CLAIMS);
        return Arrays.stream(role.split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
    private static Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }
}

validator 

FullName

package com.stu.manage.demo.validator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
/**
 * 功能描述:自定义校验FullName合法性的注解
 */
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FulleNameValidator.class)
public @interface FullName {
    String message() default "姓名格式错误";
    Class[] groups() default {};
    Class[] payload() default {};
}

FulleNameValidator

package com.stu.manage.demo.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FulleNameValidator implements ConstraintValidator {
    /**
     * 2 - 15 位字母 / 数字 / 简繁体字
     */
    private static final String FULL_NAME_REG_EXP = "(?!.*\\s$)((?=\\S)(?![0-9]+$)[\\u4E00-\\u9FA5A-Za-z0-9. ' ]{2,15})";
    @Override
    public boolean isValid(String fullNameStr, ConstraintValidatorContext context) {
        if (fullNameStr == null) {
            return true;
        }
        log.info("fullName is {}", fullNameStr);
        return fullNameStr.matches(FULL_NAME_REG_EXP);
    }
}

application.properties

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?useSSL=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#spring.datasource.initialSize=20
#spring.datasource.minIdle=50
#spring.datasource.maxActive=500
server.port=8888
#mybatis plus
mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.type-aliases-package=com.stu.manage.demo.entity
logging.level.com.stu.manage.demo.mapper = debug
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=10000
spring.redis.database=0
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0

pom.xml 



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.4.0
        
    
    com.stu-mange
    demo
    0.0.1-SNAPSHOT
    demo
    Demo project for Spring Boot
    
        1.8
        0.10.7
        29.0-jre
        4.13.1
    
    
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-validation
        
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            org.springframework.security
            spring-security-test
            test
        
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-web-services
        
        
            org.springframework.boot
            spring-boot-starter-webflux
        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            org.postgresql
            postgresql
            runtime
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            mysql
            mysql-connector-java
            runtime
        
        
            io.projectreactor
            reactor-test
            test
        
        
        com.microsoft.sqlserver
        mssql-jdbc
        runtime
        
        
        
            org.apache.httpcomponents
            httpclient
            4.5
        
        
            org.apache.httpcomponents
            httpmime
            4.5
        
        
            org.apache.httpcomponents
            httpcore
            4.4.1
        
        
            com.alibaba
            fastjson
            1.2.17
        
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.0
        
        
        
            com.alibaba
            druid
            1.1.9
        
        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        
        
        
            org.projectlombok
            lombok
            true
        
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
        
            io.jsonwebtoken
            jjwt-api
            ${jwt.version}
        
        
            io.jsonwebtoken
            jjwt-impl
            ${jwt.version}
            runtime
        
        
            io.jsonwebtoken
            jjwt-jackson
            ${jwt.version}
            runtime
        
        
        
            com.google.guava
            guava
            ${guava.version}
        
        
        
            junit
            junit
            ${junit.version}
            test
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

 


该记录引用项目:https://github.com/Snailclimb/spring-security-jwt-guide、http://t.zoukankan.com/snailclimb-p-11631890.html

你可能感兴趣的:(spring,security,spring,boot,安全,后端,java,spring)