SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

x33g5p2x  于2021-11-22 转载在 Spring  
字(23.1k)|赞(0)|评价(0)|浏览(508)

前言

本项目采用SpringBoot+SpringSecurity+Jwt+Mybatis,实现了基于数据库的用户认证以及权限分配。

pom.xml导入依赖

<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>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <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>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--自定义的处理类中需要用到-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!--注解配置处理器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
        <!--Swagger相关依赖-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>

数据库表结构

/* Navicat Premium Data Transfer Source Server : Taylor Swift Source Server Type : MySQL Source Server Version : 80023 Source Host : localhost:3306 Source Schema : security Target Server Type : MySQL Target Server Version : 80023 File Encoding : 65001 Date: 18/10/2021 18:20:16 */

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求路径匹配规则',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '/db/**');
INSERT INTO `menu` VALUES (2, '/admin/**');
INSERT INTO `menu` VALUES (3, '/user/**');

-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `mid` int NOT NULL COMMENT 'menu表外键',
  `rid` int NOT NULL COMMENT 'role表外键',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES (1, 1, 1);
INSERT INTO `menu_role` VALUES (2, 2, 2);
INSERT INTO `menu_role` VALUES (3, 3, 3);

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `nameZh` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'dba', '数据库管理员');
INSERT INTO `role` VALUES (2, 'admin', '系统管理员');
INSERT INTO `role` VALUES (3, 'user', '用户');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `enabled` tinyint(1) NULL DEFAULT NULL,
  `locked` tinyint(1) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 0);
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 0);
INSERT INTO `user` VALUES (3, 'user', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 0);
INSERT INTO `user` VALUES (4, 'Sabrina', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', 1, 1);

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `uid` int NULL DEFAULT NULL,
  `rid` int NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 2, 2);
INSERT INTO `user_role` VALUES (4, 3, 3);

SET FOREIGN_KEY_CHECKS = 1;

项目结构图

核心配置类SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    JwtUserDetailService userService;

    @Autowired
    PermissionFilter permissionFilter;

    @Autowired
    UserAccessDecisionManager userAccessDecisionManager;

    @Bean
    PermissionFilter permissionFilter()
    {
        return new PermissionFilter();
    }

    @Bean
    PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests()
                //注册PermissionFilter和UserAccessDecisionManager进行权限管理
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>()
                {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o)
                    {
                        o.setAccessDecisionManager(userAccessDecisionManager);
                        o.setSecurityMetadataSource(permissionFilter);
                        return o;
                    }
                })
            
                // 放行静态资源
                .antMatchers(
                        HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/webSocket/**"
                ).permitAll()

                // 放行文件访问
                .antMatchers("/file/**").permitAll()

                // 放行druid
                .antMatchers("/druid/**").permitAll()

                // 放行OPTIONS请求
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            
                //配置登录路径
                .antMatchers(HttpMethod.POST,"/login").permitAll()

                //其他请求都需要进行认证
                .anyRequest().authenticated()
                .and()
                //添加登录过滤器,当请求为"login"时该过滤器截取请求
                .addFilterBefore(new JwtLoginFilter("/login",
                        authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                //添加token校验过滤器,每次发起请求都需要通过该过滤器进行判断是否处于登录状态
                .addFilterBefore(new JwtTokenFilter(),UsernamePasswordAuthenticationFilter.class)
                //解决跨域问题
                .cors().and().csrf().disable();

    }

    @Override
    public void configure(WebSecurity web) throws Exception
    {

        web.ignoring()
                .antMatchers("/swagger-ui.html","v2/api-docs")
                .antMatchers("/v2/**")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/webjars/**")
                .antMatchers("/*/api-docs")
                .antMatchers("/doc.html");
    }
}

实体类

public class JwtUserDetails implements UserDetails
{

    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<Role> roles;
    private String token;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return roles == null ? new ArrayList<SimpleGrantedAuthority>()
                : roles.stream().map(role -> new SimpleGrantedAuthority(String.valueOf(role.getRoleId()))).collect(Collectors.toList());
    }

    @Override
    public String getPassword()
    {
        return this.password;
    }

    @Override
    public String getUsername()
    {
        return this.password;
    }

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

    @Override
    public boolean isAccountNonLocked()
    {
        return !locked;
    }

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

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

    public Integer getId()
    {
        return id;
    }

    public void setId(Integer id)
    {
        this.id = id;
    }

    public Boolean getEnabled()
    {
        return enabled;
    }

    public void setEnabled(Boolean enabled)
    {
        this.enabled = enabled;
    }

    public Boolean getLocked()
    {
        return locked;
    }

    public void setLocked(Boolean locked)
    {
        this.locked = locked;
    }

    public String getToken()
    {
        return token;
    }

    public void setToken(String token)
    {
        this.token = token;
    }

    public List<Role> getRoles()
    {
        return roles;
    }

    public void setRoles(List<Role> roles)
    {
        this.roles = roles;
    }

}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Menu
{
    private Integer id;
    /** * 访问路径 */
    private String pattern;
    /** * 访问当前路径所需要的角色 */
    private List<Role> roles;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role
{
    private Integer roleId;
    private String name;
    /** * 角色中文名 */
    private String nameZh;
}

工具类

@Component
@Slf4j
public class JwtTokenUtil
{

    public final static String CLAIMS_KEY_AUTHORITIES = "authorities";
    public final static String SECRET = "Turing-Team";
    public final static Long TOKEN_VALIDITY_IN_SECONDS = 600000000L;
    public void printLogger(Exception e)
    {
        log.error("Error:{}",e.getMessage());
    }

    public Claims getClaimsFromToken(String token)
    {
        Claims claims;
        try
        {
            claims = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (ExpiredJwtException e)
        {
            printLogger(e);
            claims = null;
        }catch (IncorrectClaimException e)
        {
            printLogger(e);
            claims = null;
        }
        catch (Exception e)
        {
            printLogger(e);
            claims = null;
        }
        return claims;
    }

    public String getUsernameFromToken(String token)
    {
        String username;
        try
        {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        }catch (Exception e)
        {
            printLogger(e);
            username = null;
        }
        return username;
    }

    public Date getCreateDateFromToken(String token)
    {
        Date date;
        try
        {
            Claims claims = getClaimsFromToken(token);
            date = claims.getIssuedAt();
        }catch (Exception e)
        {
            date = null;
            printLogger(e);
        }
        return date;
    }

    public Date getExpirationDateFromToken(String token)
    {
        Date date;
        try
        {
            Claims claims = getClaimsFromToken(token);
            date = claims.getExpiration();
        }
        catch (Exception e)
        {
            printLogger(e);
            date = null;
        }
        return date;
    }

    private Date generateExpirationDate()
    {
        return new Date(System.currentTimeMillis() + TOKEN_VALIDITY_IN_SECONDS);
    }

    public boolean isTokenExpired(String token)
    {
        Date date = getExpirationDateFromToken(token);
        return getClaimsFromToken(token) == null || date.before(new Date());
    }

    public String generateToken(String username,StringBuffer roles)
    {
        Claims claims = new DefaultClaims();
        claims.setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate());

        return Jwts.builder()
                .setClaims(claims)
                .claim(CLAIMS_KEY_AUTHORITIES,roles)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();

    }

    public String refreshToken(String token)
    {
        String refreshedToken;
        try
        {
            Claims claims = getClaimsFromToken(token);
            StringBuffer roles = (StringBuffer) claims.get(CLAIMS_KEY_AUTHORITIES);
            refreshedToken = generateToken(claims.getSubject(),roles);
        }catch (Exception e)
        {
            printLogger(e);
            refreshedToken = null;
        }
        return refreshedToken;
    }

    public boolean validateToken(String token, UserDetails userDetails)
    {
        JwtUserDetails user = (JwtUserDetails) userDetails;
        String username = getUsernameFromToken(token);
        return username.equals(user.getUsername()) && !isTokenExpired(token);
    }

    public Jws<Claims> parserToken(String token)throws JwtException
    {
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token);

    }

}
@Component
public class SpringInjectUtil implements ApplicationContextAware
{

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringInjectUtil.applicationContext == null){
            SpringInjectUtil.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }

    //根据name
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //根据类型
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name,clazz);
    }

}
@Data
@Configuration()
@ConfigurationProperties(prefix = "jwt")	//从application.yaml配置中获取
public class JwtSecurityProperties
{
    private String header;

    private String secret;

    private Long tokenValidityInSeconds;
}
#Jwt Configuration
jwt:
    header: Authorization
    secret: Turing-Team
    token_validity-in-seconds: 600000000

用户登录认证

public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter
{
    @Resource
    JwtTokenUtil jwtTokenUtil;

    private void init()
    {
        if (jwtTokenUtil == null)
        {
            jwtTokenUtil = (JwtTokenUtil) SpringInjectUtil.getBean("jwtTokenUtil");
        }
    }

    public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager)
    {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        init();
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException
    {
	    //从请求中获取用户信息
        String username = httpServletRequest.getParameter("username");
        String password = httpServletRequest.getParameter("password");
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
        //认证->会调用JwtUserDetailService.loadUserByUsername
        return getAuthenticationManager().authenticate(token);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException
    {
        response.setContentType("application/json;charset=utf-8");
        Result<JwtUserDetails> result = new Result<>();
        PrintWriter out = response.getWriter();
        String responseResult;

        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        StringBuffer roles = new StringBuffer();
        
        String username = request.getParameter("username");
        JwtUserDetails user = (JwtUserDetails) authResult.getPrincipal();

        //遍历用户角色,将其写入token,角色之间用逗号隔开
        List<Role> rolesList = user.getRoles();
        for (Role role : rolesList)
        {
            if (role != null)
            {
                System.out.println(role);
                roles.append(role.getRoleId()+",");
            }
        }
        //生成token
        String token = jwtTokenUtil.generateToken(username, roles);
        user.setToken(token);

        result.code(ResultCode.SUCCESS).message("Login Success").data(user);
	
        //将封装好的带有token的用户信息返回前端
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();
        out.close();
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException
    {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out =response.getWriter();
        Result<String> result = new Result<>();

        //账户是否过期/被锁定/不可用由SpringSecurity进行检查之后报出异常
        //AbstractUserDetailsAuthenticationProvider.check()中进行检查
        if (failed instanceof LockedException)
        {
            result.code(ResultCode.ERROR).message("账号已被锁定,无法进行登录操作!").data("Account had been locked");
        }
        else if (failed instanceof DisabledException)
        {
            result.code(ResultCode.ERROR).message("账号不可用,无法进行登录操作!").data("Account is disabled");
        }else
        {
            result.message("用户名或者密码错误,请重新登录!").code(ResultCode.ERROR).data("Login Failure");
        }

        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();
        out.close();
    }
}

Token令牌验证

public class JwtTokenFilter extends GenericFilterBean
{
    private static JwtTokenFilter jwtTokenFilter;

    @Resource
    JwtSecurityProperties jwtSecurityProperties;

    @Resource
    JwtTokenUtil jwtTokenUtil;

    private void init()
    {
        if (jwtSecurityProperties == null)
        {
            jwtSecurityProperties = (JwtSecurityProperties) SpringInjectUtil.getBean("jwtSecurityProperties");
        }
        if (jwtTokenUtil == null)
        {
            jwtTokenUtil = (JwtTokenUtil) SpringInjectUtil.getBean("jwtTokenUtil");
        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
    {
        init();

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //从请求头中获取token
        String token = request.getHeader(jwtSecurityProperties.getHeader());
        PrintWriter out;
        Result result;
        response.setContentType("application/json;charset=utf-8");
        if ("".equals(token) || token == null)
        {
            out = response.getWriter();
            result = new Result<String>();
            result.code(ResultCode.UNAUTHORIZED)
                    .message("必须携带用户的认证信息进行访问")
                    .data("User Unauthorized");
            out.write(new ObjectMapper().writeValueAsString(result));
            out.flush();
            out.close();
            return;
        }

        if (jwtTokenUtil.isTokenExpired(token))
        {
            out = response.getWriter();
            result = new Result<String>();
            result.code(ResultCode.UNAUTHORIZED)
                    .message("登录认证已过期,请重新登录!")
                    .data("User Authorization Is Expired");
            out.write(new ObjectMapper().writeValueAsString(result));
            out.flush();
            out.close();
            return;
        }

        Jws<Claims> jws;
        try
        {
            jws = jwtTokenUtil.parserToken(token);
        }catch (JwtException e)
        {
            out = response.getWriter();
            result = new Result<String>();
            result.code(ResultCode.UNAUTHORIZED)
                    .message("登录认证无效!")
                    .data("Invalid Authorization ");
            out.write(new ObjectMapper().writeValueAsString(result));
            out.flush();
            out.close();
            return;
        }

        Claims user = jws.getBody();
        //获取用户名
        String username = user.getSubject();
        //获取用户角色,以逗号作分割的字符串
        String authoritiesString = (String) user.get(JwtTokenUtil.CLAIMS_KEY_AUTHORITIES);
        //自动转化为GrantedAuthority对象
        List<GrantedAuthority> authorities =
                AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesString);
        //对用户信息进行校验
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username, null, authorities);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        //放行
        filterChain.doFilter(servletRequest, servletResponse);

    }
}

获取用户权限

public class PermissionFilter implements FilterInvocationSecurityMetadataSource
{
    /** * 路径匹配类,用于检查用户的请求路径是否与数据库中的对应 */
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    MenuService menuService;

    private void init()
    {
        if (menuService == null)
        {
            menuService = (MenuService) SpringInjectUtil.getBean("menuService");
        }
    }

    /** * 每次用户发送请求都会先进入此方法,分析出该请求地址需要哪些角色权限 * @param o * @return * @throws IllegalArgumentException */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException
    {
        init();
        FilterInvocation filterInvocation = (FilterInvocation) o;
        //获取请求地址
        String requestUrl = filterInvocation.getRequestUrl();
        List<Menu> menus = menuService.findAllMenusWithRoles();
        //遍历所有访问路径规则
        for (Menu menu : menus)
        {
            if (antPathMatcher.match(menu.getPattern(),requestUrl))
            {
                List<Role> roles = menu.getRoles();
                String[] rolesArray = new String[roles.size()];
                for (int i = 0; i < roles.size(); i++)
                {
                    rolesArray[i] = String.valueOf(roles.get(i).getRoleId());
                }
                return SecurityConfig.createList(rolesArray);
            }
        }
        //所有的路径都匹配不上,返回默认的标识符,表示该路径是登录即可访问的
        return SecurityConfig.createList("Login-Common");
    }

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

    /** * 是否支持该方式,返回true * @param aClass * @return */
    @Override
    public boolean supports(Class<?> aClass)
    {
        return true;
    }
}

用户权限验证

@Component
public class UserAccessDecisionManager implements AccessDecisionManager
{
    /** * 判断用户是否有访问该路径的权限 * @param authentication 可以获取用户信息 * @param o 实际上是FilterInvocation对象,可以获取请求路径 * @param collection 访问该路径所需要的角色列表 * @throws AccessDeniedException 非法访问异常 * @throws InsufficientAuthenticationException */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException
    {
        //遍历访问该路径所需要的所有角色名
        for (ConfigAttribute configAttribute : collection)
        {
            //如果是登录后即可访问
            if ("Login-Common".equals(configAttribute.getAttribute()))
            {
                if (authentication instanceof AnonymousAuthenticationToken)
                {
                    throw new AccessDeniedException("尚未登陆,请前往登录!");
                }
                return;
            }

            //获取当前用户的具有的角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

            //遍历该用户的角色并判断该角色是否有权限访问当前请求路径
            for (GrantedAuthority authority : authorities)
            {
                //将用户的角色信息与访问该路径所需要角色进行匹配
                if (authority.getAuthority().equals(configAttribute.getAttribute()))
                {
                    return;
                }
            }
        }

        throw new AccessDeniedException("权限不足,请联系管理员!");

    }

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

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

Service层实现类

@Service
public class JwtUserDetailService implements UserDetailsService
{
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        JwtUserDetails jwtUserDetails = userMapper.loadUserByUsername(username);
        if (jwtUserDetails == null)
        {
            throw new UsernameNotFoundException("用户不存在");
        }
        jwtUserDetails.setRoles(roleMapper.findRolesByUserId(jwtUserDetails.getId()));
        return jwtUserDetails;
    }

}
@Service
public class MenuServiceImpl implements MenuService
{
    @Autowired
    MenuMapper menuMapper;

    @Override
    public List<Menu> findAllMenusWithRoles()
    {
        return menuMapper.findAllMenuWithRoles();
    }
    
}
@Service
public class RoleServiceImpl implements RoleService
{
    @Autowired
    JwtUserDetailService userDetailService;
    @Autowired
    RoleMapper roleMapper;
    
    @Override
    public Role findRolesByUsername(String username)
    {
        JwtUserDetails user = (JwtUserDetails) userDetailService.loadUserByUsername(username);
        return roleMapper.findRoleByUserId(user.getId());
    }
}

统一响应类

public class Result<T>
{
    private Integer code;
    private String message;
    private T data;

    public Result() {}

    public Result message(String message)
    {
        this.message = message;
        return this;
    }

    public Result code(Integer code)
    {
        this.code = code;
        return this;
    }

    public Result data(T data)
    {
        this.data = data;
        return this;
    }
    
}

Controller

@RestController
@RequestMapping
public class UserController
{
    @GetMapping("/hello")
    public Result hello()
    {
        return new Result().message("访问公共接口成功").code(ResultCode.SUCCESS).data("Success");
    }

    @GetMapping("/db/hello")
    public Result db()
    {
        return new Result().message("访问dba角色接口成功").code(ResultCode.SUCCESS).data("Success");
    }

    @GetMapping("/admin/hello")
    public Result admin()
    {
        return new Result().message("访问admin接口成功").code(ResultCode.SUCCESS).data("Success");
    }

    @GetMapping("/user/hello")
    public Result user()
    {
        return new Result().message("访问user接口成功").code(ResultCode.SUCCESS).data("Success");
    }

}

流程解析

  • 用户发起登录请求之后,会被JwtLoginFilter所拦截,进行登录信息验证;登录成功之后会返回用户信息,并且让用户的之后发送来的请求头中携带生成的Token
  • 用户发起非登录请求之后,会被JwtTokenFilter所拦截,解析并验证请求中所带的Token,判断是否处于已登录状态。
  • 已登陆的情况下,会继续被PermissionFilter所拦截,判断用户此次访问请求需要具备哪些角色,然后将其封装起来发送至请求访问管理类UserAccessDecisionManager
  • 请求访问管理类UserAccessDecisionManager得到信息后,判断此次访问是否合法,如果合法则放行,否则将抛出异常。

测试

登录测试

登录成功:

账户被锁定:

账户不可用:

账户不存在:

公共接口测试

访问具有访问权限的接口

数据库管理员 :

管理员:

访问无访问权限的接口

用户:

以上,整个登录认证以及权限控制功能已经搭建好,可以在此基础上根据需求添加其他的业务。

该文仅作为本人学习使用,水平与能力有限,如有错误或不足欢迎指正!

相关文章