springsecurity整合jwt实现授权认证,权限分配

1.前期准备工作

1.1首先需要导入jwt依赖和springsecurity的依赖

<!--security 依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--JWT 依赖 用来做登录验证-->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.0</version>
    </dependency>

1.2 自定义application.yml配置jwt属性

# jwt令牌
jwt:
  # JWT存储的请求头
  # 正常前端请求 tokenHeader:Authorization 是key tokenHead:Bearer是它的 value 加上空格然后jwt令牌,组成一个请求
  tokenHeader: Authorization
  # JWT 加解密使用的密钥
  secret: yeb-secret
  # JWT的超期限时间(60*60*24)
  expiration: 604800
  # JWT 负载中拿到开头
  tokenHead: Bearer

1.3 编写jwt工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * jwt token 工具类
 */
@Component
@SuppressWarnings("all")
public class JwtTokenUtils {

    //用户名的key
    private static final String CLAIM_KEY_USERNAME="sub";
    //jwt创建时间
    private static final String CLAIM_KEY_CREATED="created";
    @Value("${jwt.secret}")//jwt 密钥
    private String secret;

    @Value("${jwt.expiration}")//失效时间
    private Long expiration;

    /**
     * 根据用户信息生成token
     * @param userDetails
     * @return
     */
    //用户信息通过Security中的 UserDetails 拿取
    public String generateToken(UserDetails userDetails){
        Map<String,Object> jwtToken = new HashMap<>();
        jwtToken.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
        jwtToken.put(CLAIM_KEY_CREATED,new Date());
        //根据荷载生成jwt
        return generateToken(jwtToken);
    }

    /**
     * 从token中获取登录用户名
     * @param token
     * @return
     */
    public String getUserNameFromToken(String token){
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            //通过荷载 claims 就可以拿到用户名
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 判断token是否有效
     * @param token
     * @param userDetails
     * @return
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 判断token是否可以被刷新
     * 如果过期了就可以被刷新,如果没过期就不能被刷新
     * @param token
     * @return
     */
    public boolean canRefresh(String token){
        return !isTokenExpired(token);
    }

    /**
     * 刷新token
     * @param token
     * @return
     */
    public String refreshToken(String token){
        Claims claims = getClaimsFromToken(token);
        //将创建时间改成当前时间,就相当于去刷新了
        claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }

    /**
     * 判断token是否失效
     * @param token
     * @return
     */
    private boolean isTokenExpired(String token) {
        Date expireDate = getExpiredDateFromToken(token);
        //判断token时间是否是当前时间的前面 .before
        return expireDate.before(new Date());
    }

    /**
     * 从token中获取过期时间
     * @param token
     * @return
     */
    private Date getExpiredDateFromToken(String token) {
        //从token里面获取荷载
        //因为token的过期时间有对应的数据,设置过的,荷载里面就有设置过的数据
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }


    /**
     * 从token中获取荷载
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        //拿到荷载
        Claims claims = null ;
        try {
            claims = Jwts.parser()
                    //签名
                    .setSigningKey(secret)
                    //密钥
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * 根据荷载生成 JWT TOKEN
     * @param claims
     * @return
     */
    private String generateToken(Map<String,Object> claims){
        return Jwts.builder()
                .setClaims(claims)
                //失效时间
                .setExpiration(generateExpirationDate())
                //签名
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 生成token失效时间
     * @return
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

}

1.4 登录流程

 1.5 security登录流程

 

UsernamePasswordAuthenticationFilter是我们最常用的用户名和密码认证方式的主要处理类,构造了一个UsernamePasswordAuthenticationToken对象实现类,将用请求信息封authentication

Authentication接口: 封装了用户相关信息

AuthenticationManage:定义了认证Authentication的方法,是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说AuthenticationManager一般不直接认证,AuthenticationManager接口的常用实现类ProviderManager 内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager

AuthenticationManager,ProviderManager ,AuthenticationProvider…

用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个AuthenticationProvider

DaoAuthenticationProvider:用于解析并认证 UsernamePasswordAuthenticationToken 的这样一个认证服务提供者,对应以上的几种登录方式。

UserDetailsService接口:Spring Security 会将前端填写的username 传给 UserDetailService.loadByUserName方法。我们只需要从数据库中根据用户名查找到用户信息然后封装为UserDetails的实现类返回给SpringSecurity 即可,自己不需要进行密码的比对工作,密码比对交由SpringSecurity处理。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
 

1.6 security基本原理

 2.登录流程

继承UserDetails

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("f_user")
@ApiModel(value="FUser对象")
public class FUser implements Serializable, UserDetails{
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.ASSIGN_UUID)
    @ApiModelProperty(value = "用户唯一标识")
    private String id;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "手机号")
    private String phone;

    @ApiModelProperty(value = "密码")
    private String password;
    //自定义解析器
    @JsonDeserialize(using = CustomAuthorityDeserializer.class)
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = roles
                .stream()
                //将获得的权限名字通过 SimpleGrantedAuthority 转换成授权的 url
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());

        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @TableField(exist = false)
    private List<FRole> roles;
}

UserController

@Api("用户接口")
@RestController
@RequestMapping("/user")
public class FUserController {
    @Autowired
    private AccountUtil accountUtil;
    @Autowired
    private IFUserService userService;
    @ApiOperation("是否注册")
    @RequestMapping(value ="/exist",method = RequestMethod.POST)
    public RespBean existUser(@RequestBody FUser fUser){
        String desEncrypt = accountUtil.desEncrypt(fUser.getPhone());
        FUser user = userService.getOne(new QueryWrapper<FUser>().eq("phone", desEncrypt));
        if(ObjectUtil.isEmpty(user)){
            return RespBean.success("可以注册,手机号还未注册");
        }
        return RespBean.error("手机号已注册");
    }
    @ApiOperation("注册")
    @RequestMapping(value ="/register",method = RequestMethod.PUT)
    public RespBean registerUser(@RequestBody FUser user){
        user.setPhone(accountUtil.desEncrypt(user.getPhone()));
        boolean save = userService.save(user);
        if(save){
            return RespBean.success("注册成功!");

        }
        return RespBean.error("注册失败");
    }
    @ApiOperation("登录之后返回token")
    @RequestMapping(value ="/login",method = RequestMethod.POST)
    public RespBean loginUser(@RequestBody LoginUser loginUser, HttpServletRequest request){
        return userService.loginUser(loginUser.getPhone(),loginUser.getPassword(),request);
    }
    @ApiOperation(value = "获取当前登录用户的信息")
    @GetMapping("/info")
    public FUser getUserInfo(Principal principal){
        if (principal == null){
            return null;
        }
        String username = principal.getName();
        FUser user = userService.getOneUser(username);
        user.setPassword(null);
        user.setRoles(userService.getRoles(user.getId()));
        return user;
    }


}

服务实现类,这里我采用手机号登录,可以根据手机号查出这个对象不为空的话再去获得其

username再放给loadbyusername

@Service
public class FUserServiceImpl extends ServiceImpl<FUserMapper, FUser> implements IFUserService {
    @Autowired
    private FRoleMapper roleMapper;
    @Autowired
    private JwtTokenUtils jwtTokenUtils;
    @Autowired
    private IFUserService userService;
    @Autowired
    private UserDetailsService userDetailsService;
    //密码加密
    @Autowired
    private PasswordEncoder bCryptPasswordEncoder;
    //将配置文件中存的值取过来
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Override
    public RespBean loginUser(String phone, String password, HttpServletRequest request) {
        //查询数据库中是否存在用户并判断当前登录用户密码与数据库密码是否匹配
        FUser user = userService.getUser(phone);
        if(ObjectUtil.isNull(user) || !bCryptPasswordEncoder.matches(password,user.getPassword())){
            return RespBean.error("用户名或密码不正确,请重新输入!");
        }
        UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
        //更新security上下文登录用户对象,null的位置本来该放密码,但是一般不放
        //userDetails.getAuthorities()是权限列表
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(userDetails, null,
                        userDetails.getAuthorities());
        // 放在security全局里面
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 到这里说明没问题 就让他拿到令牌
        String token = jwtTokenUtils.generateToken(userDetails);
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);//返回头部信息

        // 登录成功之后返回一个token给前端
        return RespBean.success("登录成功", tokenMap);//tokenMap中有 username,new Date(),tokenHead
    }

    @Override
    public List<FRole> getRoles(String id) {
        return roleMapper.getRoles(id);
    }

    @Override
    public FUser getUser(String phone) {
        return userService.getOne(new QueryWrapper<FUser>().eq("phone",phone));
    }

    @Override
    public FUser getOneUser(String username) {
        return userService.getOne(new QueryWrapper<FUser>().eq("username",username));
    }
}

jwt拦截器

/**
 * jwt登录授权过滤器
 */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private JwtTokenUtils jwtTokenUtils;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //通过 request 获取请求头
        String authHeader = httpServletRequest.getHeader(tokenHeader);
        //验证头部,不存在,或者不是以tokenHead:Bearer开头的
        if (authHeader != null && authHeader.startsWith(tokenHead)){
            //存在,就做一个字符串的截取,其实就是获取了登录的token
            String authToken = authHeader.substring(tokenHead.length());
            //jwt根据token获取用户名
            //token存在用户名但是未登录
            String userName = jwtTokenUtils.getUserNameFromToken(authToken);
            if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null){
                //登录
                UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
                //判断token是否有效,如果有效把他重新放到用户对象里面
                if (jwtTokenUtils.validateToken(authToken,userDetails)){
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
        //放行
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

}

securityconfig需要继承WebSecurityConfigureAdapter


/**
 * security配置类
 *
 */
//WebSecurityConfigurerAdapter 类是个适配器, 在配置的时候,需要我们自己写个配置类去继承他,然后编写自己所特殊需要的配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private IAdminService adminService;

    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    @Autowired
    private CustomFilter customFilter;

    @Autowired
    private CustomUrlDecisionManager customUrlDecisionManager;

    @Override//身份验证管理生成器
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //重写这个方法是因为,让登录的时候请求走自己重写的登录方法 UserDetailsService userDetailsService()
        //userDetailsService() 获取了用户名
        //asswordEncoder(passwordEncoder())密码匹配是通过BCryptPasswordEncoder加密来完成的
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
//        auth.userDetailsService(userDetailsService()).passwordEncoder(NoOpPasswordEncoder.getInstance());
    }


    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                //放行的资源路径
                "/login",
                "/logout",
                "/css/**",
                "/js/**",
                "/index.html",
                "favicon.ico",
                "/doc.html",
                "/captcha",
                "/webjars/**",
                "/swagger-resources/**",
                "/v2/api-docs/**",
                "/ws/**"
        );
    }

    //security完整的配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //使用jwt不需要csrf
        http.csrf()
                .disable()//意思 使残废,关闭后面and之前的配置
                //基于token存储登录用户信息,不需要session,关闭session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //所有请求都要求认证
                .anyRequest()
                .authenticated()
                //动态权限,获取不同菜单列表
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(customUrlDecisionManager);
                        o.setSecurityMetadataSource(customFilter);
                        return o;
                    }
                })
                .and()
                //禁用缓存
                .headers()
                .cacheControl();

        //添加 jwt 登录授权拦截器
        http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定义 未授权 和 未登录 结果返回
        http.exceptionHandling()
                //自定义 未授权
                .accessDeniedHandler(restfulAccessDeniedHandler)
                //自定义 未登录结果返回
                .authenticationEntryPoint(restAuthorizationEntryPoint);

    }

    @Override//重写获取用户的方法
    @Bean
    public UserDetailsService userDetailsService(){
        return username -> {
            Admin admin = adminService.getAdminByUserName(username);
            if (admin != null){
                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin;
            }
            throw new UsernameNotFoundException("用户名或密码不正确!");
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        //Security中默认的密码实现  BCryptPasswordEncoder
        return new BCryptPasswordEncoder();
    }

    //拦截器
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }

}

权限控制过滤器

/**
 * 权限控制
 * 根据请求url分析请求所需的角色
 */
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private IFMenuService menuService;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //获取请求的url
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        //根据角色去查询所有的菜单
        List<FMenu> menus = menuService.getMenuRoleList();
        //同时查询所有菜单的权限
        List<FMenu> menusAll = menuService.list();
        for (FMenu fMenu : menusAll) {
            if(fMenu.getUrl().equals(requestUrl)){
                for (FMenu menu : menus){
                    //判断请求 url 与菜单角色是否匹配,如果这个url在菜单中存在但是没有匹配上就直接抛异常
                    if (antPathMatcher.match(menu.getUrl(),requestUrl)){
                        String[] strings = menu.getRoles().stream().map(FRole::getName).toArray(String[]::new);
                        return SecurityConfig.createList(strings);
                    }
                }
                throw new AccessDeniedException("权限不足,请联系管理员!");
            }
        }
        //没匹配的 url 默认登录就可以访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

根据角色去匹配对应的url是否有权限


/**
 * @description: 判断用户角色
 * @author: Honors
 * @create: 2021-07-14 14:29
 */
@Slf4j
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            //当前 url 所需要的角色
            String needRole = configAttribute.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)){
                if (authentication instanceof AnonymousAuthenticationToken){
                    throw new AccessDeniedException("尚未登录,请登录!");
                }else {
                    throw new AccessDeniedException("权限不足,请联系管理员!");
                }
            }
            //判断用户角色是否为url所需要角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                log.info("当前authority:"+authority.getAuthority());
                if (authority.getAuthority().equals(needRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

自定义序列化

/**
 * @description: 自定义authority字段解析器
 * @author: Honors
 */
public class CustomAuthorityDeserializer extends JsonDeserializer {
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ObjectMapper mapper = (ObjectMapper) p.getCodec();
        JsonNode jsonNode = mapper.readTree(p);
        List<GrantedAuthority> grantedAuthorities = new LinkedList<>();
        Iterator<JsonNode> elements = jsonNode.elements();
        while (elements.hasNext()){
            JsonNode next = elements.next();
            //找到authority字段
            JsonNode authority = next.get("authority");
            //序列号,让json可以解析
            grantedAuthorities.add(new SimpleGrantedAuthority(authority.asText()));
        }
        return grantedAuthorities;
    }
}

Security自定义返回结果

/**
 * 当未登录或者token失效时访问接口是,自定义返回结果
 */
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        //设置数据格式为json格式
        response.setContentType("application/json");
        //拿到输出流
        PrintWriter out = response.getWriter();
        //未登录或失效
        RespBean bean = RespBean.error("未登录或用户信息过期,请重新登录!");
        bean.setCode(401);
        out.write(new ObjectMapper().writeValueAsString(bean));
        out.flush();
        out.close();
    }
}
/**
 * 自定义授权
 * 当访问接口没有权限时,自定义返回结果
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        RespBean error = RespBean.error("权限不足,请联系管理员!");
        error.setCode(403);
        out.write(new ObjectMapper().writeValueAsString(error));
        out.flush();
        out.close();
    }
}

根据用户id查找角色,便于后面拿角色与根据url请求的角色做对比

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="wqm.store.mapper.FRoleMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="wqm.store.pojo.FRole">
        <id column="id" property="id" />
        <result column="name" property="name" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name
    </sql>
    <select id="getRoles" resultMap="getRoles" parameterType="String">
        select r.id,r.name from f_user_role ur,f_role r where ur.uid = #{id}
        and ur.rid = r.id
    </select>
    <resultMap id="getRoles" type="FRole" extends="BaseResultMap">

    </resultMap>


</mapper>
@Repository
public interface FRoleMapper extends BaseMapper<FRole> {

    List<FRole> getRoles(@Param("id")String id);

}
public interface IFUserService extends IService<FUser> {

    RespBean loginUser(String phone, String password, HttpServletRequest request);

    FUser getUser(String phone);

    FUser getOneUser(String username);

    List<FRole> getRoles(String id);
}

根据url查询所需角色

<select id="getMenus" resultMap="getMenus">
        select m.*,r.id as rid,r.name from f_role r, f_menu_role mr,f_menu m where r.id = mr.rid and mr.mid = m.id;
    </select>
    <resultMap id="getMenus" type="FMenu" extends="BaseResultMap">
        <collection property="roles" ofType="FRole">
            <id property="id" column="rid"/>
            <result property="name" column="name"/>
        </collection>
    </resultMap>
@Repository
public interface FMenuMapper extends BaseMapper<FMenu> {
    List<FMenu> getMenus();

}
public interface IFMenuService extends IService<FMenu> {

    List<FMenu> getMenuRoleList();
}
@Service
public class FMenuServiceImpl extends ServiceImpl<FMenuMapper, FMenu> implements IFMenuService {
    @Autowired
    private FMenuMapper menuMapper;
    @Override
    public List<FMenu> getMenuRoleList() {
        return menuMapper.getMenus();
    }
}

Swagger配置,添加authrozation字段用来存放token 

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean//规定扫描哪些包下面生成swagger2文档
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                //选择扫面哪个包
                .select()
                .apis(RequestHandlerSelectors.basePackage("wqm.store.api.controller"))
                //所有的路径都可以
                .paths(PathSelectors.any())
                .build()
               //给swagger2令牌,不然测试接口太繁琐,需要登录会被拦截
                .securityContexts(securityContexts())//全局
                .securitySchemes(securitySchemes());//安全计划
    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                //标题
                .title("接口文档")
                //描述
                .description("")
                .contact(new Contact("魏青冕","http://localhost:8080/doc.html","3139596057@qq.com"))
                .version("1.0")
                .build();
    }
    private List<ApiKey> securitySchemes(){
        //设置请求头信息
        List<ApiKey> result = new ArrayList<>();
        //令牌
        ApiKey apikey = new ApiKey("authorization","authorization","Header");
        result.add(apikey);
        return result;
    }

    private List<SecurityContext> securityContexts(){
        //设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        result.add(getContextBypath("/test3/.*"));
        return result;
    }

    private SecurityContext getContextBypath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex(pathRegex))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("Authorization",authorizationScopes));
        return result;
    }

}

测试如下

 

 

 

 3.RBAC模型设计

/*
 Navicat Premium Data Transfer

 Source Server         : 丫丫
 Source Server Type    : MySQL
 Source Server Version : 80028
 Source Host           : localhost:3306
 Source Schema         : fishstore

 Target Server Type    : MySQL
 Target Server Version : 80028
 File Encoding         : 65001

 Date: 09/08/2022 13:28:21
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for f_menu
-- ----------------------------
DROP TABLE IF EXISTS `f_menu`;
CREATE TABLE `f_menu`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `url` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of f_menu
-- ----------------------------
INSERT INTO `f_menu` VALUES ('67796ddf8305e03625ebdba3195ec312', '/test1');
INSERT INTO `f_menu` VALUES ('b443e9ad4898f4e5897274371562df76', '/test2');

-- ----------------------------
-- Table structure for f_menu_role
-- ----------------------------
DROP TABLE IF EXISTS `f_menu_role`;
CREATE TABLE `f_menu_role`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `rid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `mid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of f_menu_role
-- ----------------------------
INSERT INTO `f_menu_role` VALUES ('db268eac1fc15596e175b46683066cb8', 'ab363ec9a5766ee42b15fc554d2e7c21', '67796ddf8305e03625ebdba3195ec312');

-- ----------------------------
-- Table structure for f_role
-- ----------------------------
DROP TABLE IF EXISTS `f_role`;
CREATE TABLE `f_role`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of f_role
-- ----------------------------
INSERT INTO `f_role` VALUES ('ab363ec9a5766ee42b15fc554d2e7c21', 'ROLE_stu');

-- ----------------------------
-- Table structure for f_user
-- ----------------------------
DROP TABLE IF EXISTS `f_user`;
CREATE TABLE `f_user`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户唯一标识',
  `username` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',
  `phone` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of f_user
-- ----------------------------
INSERT INTO `f_user` VALUES ('1ef48d092db370165b8e4cdfac9a0187', 'cjj', '13456789457', '$2a$10$ogvUqZZAxrBwrmVI/e7.SuFYyx8my8d.9zJ6bs9lPKWvbD9eefyCe');
INSERT INTO `f_user` VALUES ('2ff299d16d007aa37007394bae9bbcac', '会飞的鱼', '15295675946', '$2a$10$ogvUqZZAxrBwrmVI/e7.SuFYyx8my8d.9zJ6bs9lPKWvbD9eefyCe');

-- ----------------------------
-- Table structure for f_user_role
-- ----------------------------
DROP TABLE IF EXISTS `f_user_role`;
CREATE TABLE `f_user_role`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `uid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户主键',
  `rid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色主键',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of f_user_role
-- ----------------------------
INSERT INTO `f_user_role` VALUES ('695208cf31b61627c00bd987cfdd22e7', '2ff299d16d007aa37007394bae9bbcac', 'ab363ec9a5766ee42b15fc554d2e7c21');

SET FOREIGN_KEY_CHECKS = 1;

定义参考:RBAC权限模型[完整] - 简书 (jianshu.com)

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值