龙空技术网

Spring Security实现统一登录与权限控制

java小悠 1002

前言:

现在朋友们对“java登录接口”可能比较重视,兄弟们都需要分析一些“java登录接口”的相关知识。那么小编同时在网上汇集了一些对于“java登录接口””的相关资讯,希望各位老铁们能喜欢,小伙伴们快快来学习一下吧!

1 项目介绍

最开始是一个单体应用,所有功能模块都写在一个项目里,后来觉得项目越来越大,于是决定把一些功能拆分出去,形成一个一个独立的微服务,于是就有个问题了,登录、退出、权限控制这些东西怎么办呢?总不能每个服务都复制一套吧,最好的方式是将认证与鉴权也单独抽离出来作为公共的服务,业务系统只专心做业务接口开发即可,完全不用理会权限这些与之不相关的东西了。于是,便有了下面的架构图:

下面重点看一下统一认证中心和业务网关的建设

2 统一认证中心

这里采用 Spring Security + Spring Security OAuth2OAuth2是一种认证授权的协议,是一种开放的标准。最长用到的是授权码模式和密码模式,在本例中,用这两种模式都可以。首先,引入相关依赖最主要的依赖是 spring-cloud-starter-oauth2 ,引入它就够了

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-oauth2</artifactId>  <version>2.2.5.RELEASE</version></dependency>

这里Spring Boot的版本是2.6.3 完整的pom如下:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="; xmlns:xsi=";         xsi:schemaLocation=" ;>    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.tgf</groupId>        <artifactId>tgf-service-parent</artifactId>        <version>1.3.0</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.soa.supervision.uaa</groupId>    <artifactId>soas-uaa</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>soas-uaa</name>    <properties>        <java.version>1.8</java.version>        <spring-cloud.version>2021.0.0</spring-cloud.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-data-redis</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-oauth2</artifactId>            <version>2.2.5.RELEASE</version>        </dependency>        <dependency>            <groupId>com.nimbusds</groupId>            <artifactId>nimbus-jose-jwt</artifactId>            <version>9.19</version>        </dependency>        <dependency>            <groupId>com.baomidou</groupId>            <artifactId>mybatis-plus-boot-starter</artifactId>        </dependency>        <dependency>            <groupId>com.baomidou</groupId>            <artifactId>mybatis-plus-generator</artifactId>            <version>3.5.1</version>        </dependency>        <dependency>            <groupId>org.mybatis.scripting</groupId>            <artifactId>mybatis-freemarker</artifactId>            <version>1.2.3</version>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <scope>runtime</scope>        </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>    </dependencies>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring-cloud.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>                <configuration>                    <excludes>                        <exclude>                            <groupId>org.projectlombok</groupId>                            <artifactId>lombok</artifactId>                        </exclude>                    </excludes>                </configuration>            </plugin>        </plugins>    </build></project>
配置授权服务器

在授权服务器中,主要是配置如何生成Token,以及注册的客户端有哪些

package com.soa.supervision.uaa.config;import com.soa.supervision.uaa.constant.AuthConstants;import com.soa.supervision.uaa.domain.SecurityUser;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.ClassPathResource;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;import org.springframework.security.oauth2.common.OAuth2AccessToken;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;import org.springframework.security.oauth2.provider.OAuth2Authentication;import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;import org.springframework.security.oauth2.provider.endpoint.TokenKeyEndpoint;import org.springframework.security.oauth2.provider.token.TokenEnhancer;import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;import javax.annotation.Resource;import javax.sql.DataSource;import java.security.KeyPair;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * 授权服务器配置 * 1、配置客户端 * 2、配置Access_Token生成 * * @Author ChengJianSheng * @Date 2022/2/14 */@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {    @Resource    private DataSource dataSource;    @Autowired    private AuthenticationManager authenticationManager;    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        clients.withClientDetails(new JdbcClientDetailsService(dataSource));    }    @Override    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {        security.allowFormAuthenticationForClients();//        security.tokenKeyAccess("permitAll()");    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        List<TokenEnhancer> tokenEnhancerList = new ArrayList<>();        tokenEnhancerList.add(jwtTokenEnhancer());        tokenEnhancerList.add(jwtAccessTokenConverter());        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();        tokenEnhancerChain.setTokenEnhancers(tokenEnhancerList);        endpoints.accessTokenConverter(jwtAccessTokenConverter())                .tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);    }    /**     * Token增强     */    public TokenEnhancer jwtTokenEnhancer() {        return new TokenEnhancer() {            @Override            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {                SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();                Map<String, Object> additionalInformation = new HashMap<>();                additionalInformation.put(AuthConstants.JWT_USER_ID_KEY, securityUser.getUserId());                additionalInformation.put(AuthConstants.JWT_USER_NAME_KEY, securityUser.getUsername());                additionalInformation.put(AuthConstants.JWT_DEPT_ID_KEY, securityUser.getDeptId());                ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(additionalInformation);                return accessToken;            }        };    }    /**     * 采用RSA加密算法对JWT进行签名     */    public JwtAccessTokenConverter jwtAccessTokenConverter() {        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();        jwtAccessTokenConverter.setKeyPair(keyPair());        return jwtAccessTokenConverter;    }    /**     * 密钥对     */    @Bean    public KeyPair keyPair() {        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());    }    @Bean    public TokenKeyEndpoint tokenKeyEndpoint() {        return new TokenKeyEndpoint(jwtAccessTokenConverter());    }}

说明:

客户端是从数据库加载的密码模式下必须设置一个AuthenticationManager采用JWT生成token是因为它轻量级,无需存储可以减小服务端的存储压力。但是,为了实现退出功能,不得不将它存储到Redis中必须要对JWT进行加密,资源服务器在拿到客户端传的token时会去校验该token是否合法,否则客户端可能伪造token此处对token进行了增强,在token中加了几个字段分别表示用户ID和部门ID

客户端表结构如下:

DROP TABLE IF EXISTS `oauth_client_details`;CREATE TABLE `oauth_client_details`  (  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端ID',  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客户端密钥',  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '授权类型',  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `access_token_validity` int(11) NULL DEFAULT NULL COMMENT 'access_token的有效时间',  `refresh_token_validity` int(11) NULL DEFAULT NULL COMMENT 'refresh_token的有效时间',  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否允许自动授权',  PRIMARY KEY (`client_id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;INSERT INTO `oauth_client_details` VALUES ('hello', 'order-resource', '$2a$10$1Vun/h63tI4C48BqLsy2Zel5q5M2VW6w8KThoMfxww49wf9uv/dKy', 'all', 'authorization_code,password,refresh_token', ';, NULL, 7200, 7260, NULL, 'true');INSERT INTO `oauth_client_details` VALUES ('sso-client-1', NULL, '$2a$10$CxEwmODmsp/HOB7LloeBJeqUjotmNzjpk2WmjxtPxAeOYifQWLfhW', 'all', 'authorization_code', ';, NULL, 180, 240, NULL, 'true');

本例中采用RSA非对称加密,密钥文件用的是java自带的keytools生成的

将来,认证服务器用私钥对token加密,然后将公钥公开

package com.soa.supervision.uaa.controller;import com.nimbusds.jose.jwk.JWKSet;import com.nimbusds.jose.jwk.RSAKey;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.security.KeyPair;import java.security.interfaces.RSAPublicKey;import java.util.Map;/** * @Author ChengJianSheng * @Date 2022/2/15 */@RestControllerpublic class KeyPairController {    @Autowired    private KeyPair keyPair;    @GetMapping("/rsa/publicKey")    public Map<String, Object> getKey() {        RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();        RSAKey key = new RSAKey.Builder(publicKey).build();        return new JWKSet(key).toJSONObject();    }}
配置WebSecurity

在WebSecurity中主要是配置用户,以及哪些请求需要认证以后才能访问

package com.soa.supervision.uaa.config;import com.soa.supervision.uaa.service.impl.UserDetailsServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;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.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;/** * @Author ChengJianSheng * @Date 2022/2/14 */@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private UserDetailsServiceImpl userDetailsService;    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()                .antMatchers("/rsa/publicKey", "/menu/tree").permitAll()                .anyRequest().authenticated()                .and().formLogin().permitAll()                .and()                .csrf().disable();    }    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());    }    @Bean    @Override    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }}

UserDetailsService实现类

package com.soa.supervision.uaa.service.impl;import com.soa.supervision.uaa.domain.AuthUserDTO;import com.soa.supervision.uaa.domain.SecurityUser;import com.soa.supervision.uaa.service.SysUserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.LockedException;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;import java.util.Set;import java.util.stream.Collectors;/** * @Author ChengJianSheng * @Date 2022/2/14 */@Servicepublic class UserDetailsServiceImpl implements UserDetailsService {    @Autowired    private SysUserService sysUserService;    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        AuthUserDTO authUserDTO = sysUserService.getAuthUserByUsername(username);        if (null == authUserDTO) {            throw new UsernameNotFoundException("用户不存在");        }        if (!authUserDTO.isEnabled()) {            throw new LockedException("账号被禁用");        }        Set<SimpleGrantedAuthority> authorities = authUserDTO.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());        return new SecurityUser(authUserDTO.getUserId(), authUserDTO.getDeptId(), authUserDTO.getUsername(), authUserDTO.getPassword(), authUserDTO.isEnabled(), authorities);    }}

SysUserService

package com.soa.supervision.uaa.service;import com.soa.supervision.uaa.domain.AuthUserDTO;import com.soa.supervision.uaa.entity.SysUser;import com.baomidou.mybatisplus.extension.service.IService;/** * <p> * 用户表 服务类 * </p> * * @author ChengJianSheng * @since 2022-02-14 */public interface SysUserService extends IService<SysUser> {    AuthUserDTO getAuthUserByUsername(String username);}

AuthUserDTO

package com.soa.supervision.uaa.domain;import lombok.Data;import java.io.Serializable;import java.util.List;/** * @Author ChengJianSheng * @Date 2022/2/15 */@Datapublic class AuthUserDTO implements Serializable {    private Integer userId;    private String username;    private String password;    private Integer deptId;    private boolean enabled;    private List<String> roles;}

SysUserServiceImpl

package com.soa.supervision.uaa.service.impl;import com.soa.supervision.uaa.domain.AuthUserDTO;import com.soa.supervision.uaa.entity.SysUser;import com.soa.supervision.uaa.mapper.SysUserMapper;import com.soa.supervision.uaa.service.SysUserService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * <p> * 用户表 服务实现类 * </p> * * @author ChengJianSheng * @since 2022-02-14 */@Servicepublic class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {    @Autowired    private SysUserMapper sysUserMapper;    @Override    public AuthUserDTO getAuthUserByUsername(String username) {        return sysUserMapper.selectAuthUserByUsername(username);    }}

SysUserMapper

package com.soa.supervision.uaa.mapper;import com.soa.supervision.uaa.domain.AuthUserDTO;import com.soa.supervision.uaa.entity.SysUser;import com.baomidou.mybatisplus.core.mapper.BaseMapper;/** * 用户表 Mapper 接口 * * @author ChengJianSheng * @since 2022-02-14 */public interface SysUserMapper extends BaseMapper<SysUser> {    AuthUserDTO selectAuthUserByUsername(String username);}

SysUserMapper.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ";><mapper namespace="com.soa.supervision.uaa.mapper.SysUserMapper">        <resultMap id="authUserResultMap" type="com.soa.supervision.uaa.domain.AuthUserDTO">        <id property="userId" column="id"/>        <result property="username" column="username"/>        <result property="password" column="password"/>        <result property="deptId" column="dept_id"/>        <result property="enabled" column="enabled"/>        <collection property="roles" ofType="string" javaType="list">            <result column="role_code"/>        </collection>    </resultMap>    <!-- 根据用户名查用户 -->    <select id="selectAuthUserByUsername" resultMap="authUserResultMap">        SELECT            t1.id,            t1.username,            t1.`password`,            t1.dept_id,            t1.enabled,            t3.`code` AS role_code        FROM            sys_user t1                LEFT JOIN sys_user_role t2 ON t1.id = t2.user_id                LEFT JOIN sys_role t3 ON t2.role_id = t3.id        WHERE            t1.username = #{username}    </select></mapper>

UserDetails

package com.soa.supervision.uaa.domain;import lombok.AllArgsConstructor;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;import java.util.Set;/** * @Author ChengJianSheng * @Date 2022/2/14 */@AllArgsConstructorpublic class SecurityUser implements UserDetails {    /**     * 扩展字段     */    private Integer userId;    private Integer deptId;    private String username;    private String password;    private boolean enabled;    private Set<SimpleGrantedAuthority> authorities;    @Override    public Collection<? extends GrantedAuthority> 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 enabled;    }    public Integer getUserId() {        return userId;    }    public Integer getDeptId() {        return deptId;    }}
登录

默认的登录url是/login,本例中没有自定义登录页面,而是使用默认的登录页面 正常的密码模式下,输入用户名和密码,登录成功以后返回token。本例中使用密码模式,所以写了个登录接口,而且也是取巧,覆盖了默认的/oauth/token端点

package com.soa.supervision.uaa.controller;import com.tgf.common.domain.RespResult;import com.tgf.common.util.RespUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.oauth2.common.OAuth2AccessToken;import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;import org.springframework.web.HttpRequestMethodNotSupportedException;import org.springframework.web.bind.annotation.*;import java.security.Principal;import java.util.HashMap;import java.util.Map;/** * @Author ChengJianSheng * @Date 2022/2/18 */@RestController@RequestMapping("/oauth")public class AuthorizationController {    @Autowired    private TokenEndpoint tokenEndpoint;    /**     * 密码模式 登录     * @param principal     * @param parameters     * @return     * @throws HttpRequestMethodNotSupportedException     */    @PostMapping("/token")    public RespResult postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();        Map<String, Object> map = new HashMap<>();        //  缓存        return RespUtils.success();    }    /**     * 退出     * @return     */    @PostMapping("/logout")    public RespResult logout() {//        JSONObject payload = JwtUtils.getJwtPayload();//        String jti = payload.getStr(SecurityConstants.JWT_JTI); // JWT唯一标识//        Long expireTime = payload.getLong(SecurityConstants.JWT_EXP); // JWT过期时间戳(单位:秒)//        if (expireTime != null) {//            long currentTime = System.currentTimeMillis() / 1000;// 当前时间(单位:秒)//            if (expireTime > currentTime) { // token未过期,添加至缓存作为黑名单限制访问,缓存时间为token过期剩余时间//                redisTemplate.opsForValue().set(SecurityConstants.TOKEN_BLACKLIST_PREFIX + jti, null, (expireTime - currentTime), TimeUnit.SECONDS);//            }//        } else { // token 永不过期则永久加入黑名单//            redisTemplate.opsForValue().set(SecurityConstants.TOKEN_BLACKLIST_PREFIX + jti, null);//        }//        return Result.success("注销成功");        return RespUtils.success();    }}
补充:授权码模式获取access_token菜单

登录以后,前端会查询菜单并展示,下面是菜单相关接口 SysMenuController

package com.soa.supervision.uaa.controller;import com.soa.supervision.uaa.domain.MenuVO;import com.soa.supervision.uaa.service.SysMenuService;import com.tgf.common.domain.RespResult;import com.tgf.common.util.RespUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Arrays;import java.util.List;/** * <p> * 菜单表 前端控制器 * </p> * * @author ChengJianSheng * @since 2022-02-21 */@RestController@RequestMapping("/menu")public class SysMenuController {    @Autowired    private SysMenuService sysMenuService;    @GetMapping("/tree")    public RespResult tree(String systemCode) {        List<Integer> roleIds = Arrays.asList(1,2);        List<MenuVO> voList = sysMenuService.getMenuByUserRoles(systemCode, roleIds);        return RespUtils.success(voList);    }}

SysMenuService

package com.soa.supervision.uaa.service;import com.soa.supervision.uaa.domain.MenuVO;import com.soa.supervision.uaa.entity.SysMenu;import com.baomidou.mybatisplus.extension.service.IService;import java.util.List;/** * <p> * 菜单表 服务类 * </p> * * @author ChengJianSheng * @since 2022-02-21 */public interface SysMenuService extends IService<SysMenu> {    List<MenuVO> getMenuByUserRoles(String systemCode, List<Integer> roleIds);}

SysMenuServiceImpl

package com.soa.supervision.uaa.service.impl;import com.soa.supervision.uaa.domain.MenuVO;import com.soa.supervision.uaa.entity.SysMenu;import com.soa.supervision.uaa.mapper.SysMenuMapper;import com.soa.supervision.uaa.service.SysMenuService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;/** * <p> * 菜单表 服务实现类 * </p> * * @author ChengJianSheng * @since 2022-02-21 */@Servicepublic class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {    @Autowired    private SysMenuMapper sysMenuMapper;    /**     * 构造菜单树     * @param systemCode     * @param roleIds     * @return     */    @Override    public List<MenuVO> getMenuByUserRoles(String systemCode, List<Integer> roleIds) {        List<MenuVO> voList = new ArrayList<>();        List<SysMenu> sysMenuList = sysMenuMapper.selectMenuByRole(systemCode, roleIds);        if (null == sysMenuList || sysMenuList.size() == 0) {            return voList;        }        List<MenuVO> menuVOList = sysMenuList.stream().map(e->{            MenuVO vo = new MenuVO();            BeanUtils.copyProperties(e, vo);            vo.setChildren(new ArrayList<>());            return vo;        }).distinct().collect(Collectors.toList());        for (int i = 0; i < menuVOList.size(); i++) {            for (int j = 0; j < menuVOList.size(); j++) {                if (menuVOList.get(i).getId().equals(menuVOList.get(j).getId())) {                    continue;                }                if (menuVOList.get(i).getId().equals(menuVOList.get(j).getParentId())) {                    menuVOList.get(i).getChildren().add(menuVOList.get(j));                }            }        }        return menuVOList.stream().filter(e->0==e.getParentId()).collect(Collectors.toList());    }}

MenuVO

package com.soa.supervision.uaa.domain;import lombok.Data;import java.io.Serializable;import java.util.List;/** * @Author ChengJianSheng * @Date 2022/2/21 */@Datapublic class MenuVO implements Serializable {    private Integer id;    /**     * 菜单名称     */    private String name;    /**     * 父级菜单ID     */    private Integer parentId;    /**     * 路由地址     */    private String routePath;    /**     * 组件     */    private String component;    /**     * 图标     */    private String icon;    /**     * 排序号     */    private Integer sort;    /**     * 子菜单     */    private List<MenuVO> children;}

SysMenuMapper

package com.soa.supervision.uaa.mapper;import com.soa.supervision.uaa.entity.SysMenu;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import org.apache.ibatis.annotations.Param;import java.util.List;/** * <p> * 菜单表 Mapper 接口 * </p> * * @author ChengJianSheng * @since 2022-02-21 */public interface SysMenuMapper extends BaseMapper<SysMenu> {    List<SysMenu> selectMenuByRole(@Param("systemCode") String systemCode, @Param("roleIds") List<Integer> roleIds);}

SysMenuMapper.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ";><mapper namespace="com.soa.supervision.uaa.mapper.SysMenuMapper">    <!-- 根据角色查菜单 -->    <select id="selectMenuByRole" resultType="com.soa.supervision.uaa.entity.SysMenu">        SELECT            t1.*        FROM            sys_menu t1                LEFT JOIN sys_role_menu t2 ON t1.id = t2.menu_id        WHERE            t1.system_code = #{systemCode}          AND t1.hidden = 0          AND t2.role_id IN <foreach collection="roleIds" item="roleId" open="(" close=")" separator=",">#{roleId}</foreach>        ORDER BY            t1.sort ASC    </select></mapper>

application.yml

server:  port: 8094  servlet:    context-path: /soas-uaaspring:  application:    name: soas-uaa  datasource:    url: jdbc:mysql://192.168.28.22:3306/demo?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false    driver-class-name: com.mysql.cj.jdbc.Driver    username: root    password: 1234567  redis:    host: 192.168.28.01    port: 6379    password: 123456logging:  level:    org:      springframework:        security: debugmybatis-plus:  configuration:    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3 网关

在这里,网关相当于OAuth2中的资源服务器这么个角色。网关代理了所有的业务微服务,如果说那些业务服务是资源的,那么网关就是资源的集合,访问网关就是访问资源,访问资源就要先认证再授权才能访问。同时,网关又相当于一个公共方法,因此在这里做鉴权是比较合适的。

首先是依赖

<?xml version="1.0" encoding="UTF-8"?><project xmlns="; xmlns:xsi=";         xsi:schemaLocation=" ;>    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.tgf</groupId>        <artifactId>tgf-service-parent</artifactId>        <version>1.3.1-SNAPSHOT</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.soa.supervision.gateway</groupId>    <artifactId>soas-gateway</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>soas-gateway</name>    <properties>        <java.version>1.8</java.version>        <spring-security.version>5.6.1</spring-security.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-gateway</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-data-redis</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.security</groupId>            <artifactId>spring-security-config</artifactId>            <version>${spring-security.version}</version>        </dependency>        <dependency>            <groupId>org.springframework.security</groupId>            <artifactId>spring-security-oauth2-resource-server</artifactId>            <version>${spring-security.version}</version>        </dependency>        <dependency>            <groupId>org.springframework.security</groupId>            <artifactId>spring-security-oauth2-jose</artifactId>            <version>${spring-security.version}</version>        </dependency>        <!-- spring-security-oauth2-jose的依赖中包含了nimbus-jose-jwt,只是版本不是最新的而已,这里如果想使用更高版本的nimbus-jose-jwt的话可以重新声明一下 -->        <dependency>            <groupId>com.nimbusds</groupId>            <artifactId>nimbus-jose-jwt</artifactId>            <version>9.15.2</version>        </dependency>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>        </dependency>        <dependency>            <groupId>org.apache.commons</groupId>            <artifactId>commons-collections4</artifactId>        </dependency>        <dependency>            <groupId>cn.hutool</groupId>            <artifactId>hutool-all</artifactId>            <version>5.7.21</version>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <optional>true</optional>        </dependency>    </dependencies>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring-cloud.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>                <configuration>                    <excludes>                        <exclude>                            <groupId>org.projectlombok</groupId>                            <artifactId>lombok</artifactId>                        </exclude>                    </excludes>                </configuration>            </plugin>        </plugins>    </build></project>

application.yml

server:  port: 8090spring:  cloud:    gateway:      routes:        - id: soas-enterprise          uri:           predicates:            - Path=/soas-enterprise/**        - id: soas-portal          uri:           predicates:            - Path=/soas-portal/**        - id: soas-finance          uri:           predicates:            - Path=/soas-finance/**      discovery:        locator:          enabled: false  redis:    host: 192.168.28.01    port: 6379    password: 123456    database: 9  security:    oauth2:      resourceserver:        jwt:          jwk-set-uri: :  ignore:    urls:      - /soas-portal/auth/**

直接放行的url

package com.soa.supervision.gateway.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/** * @Author ChengJianSheng * @Date 2021/12/15 */@Data@Component@ConfigurationProperties(prefix = "secure.ignore")public class IgnoreUrlProperties {    private String[] urls;}

logback.xml

<?xml version="1.0" encoding="UTF-8"?><configuration scan="true" scanPeriod="30 seconds" debug="false">    <property name="log.charset" value="utf-8" />    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" />    <property name="log.dir" value="./logs" />    <!--输出到控制台-->    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">        <encoder>            <pattern>${log.pattern}</pattern>            <charset>${log.charset}</charset>        </encoder>    </appender>    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">        <file>${log.dir}/soas-gateway.log</file>        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">            <fileNamePattern>${log.dir}/soas-gateway.%d{yyyy-MM-dd}.log</fileNamePattern>            <maxHistory>30</maxHistory>            <totalSizeCap>3GB</totalSizeCap>        </rollingPolicy>        <encoder>            <pattern>${log.pattern}</pattern>        </encoder>    </appender>    <root level="info">        <appender-ref ref="console" />        <appender-ref ref="file" />    </root></configuration>
鉴权

真正的权限判断或者说权限控制是在这里,下面这段代码尤为重要,而且它在整个网关过滤器之前调用

package com.soa.supervision.gateway.config;import com.alibaba.fastjson.JSON;import com.soa.supervision.gateway.constant.AuthConstants;import com.soa.supervision.gateway.constant.RedisConstants;import lombok.extern.slf4j.Slf4j;import org.apache.commons.collections4.CollectionUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.security.authorization.AuthorizationDecision;import org.springframework.security.authorization.ReactiveAuthorizationManager;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.web.server.authorization.AuthorizationContext;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;import org.springframework.util.PathMatcher;import reactor.core.publisher.Mono;import java.util.ArrayList;import java.util.List;import java.util.Map;/** * @Author ChengJianSheng * @Date 2022/2/16 */@Slf4j@Componentpublic class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {    private final PathMatcher pathMatcher = new AntPathMatcher();    @Autowired    private StringRedisTemplate stringRedisTemplate;    @Override    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext context) {        ServerHttpRequest request = context.getExchange().getRequest();        String path = request.getURI().getPath();        //  token不能为空且有效        String token = request.getHeaders().getFirst(AuthConstants.JWT_TOKEN_HEADER);        if (StringUtils.isBlank(token) || !token.startsWith(AuthConstants.JWT_TOKEN_PREFIX)) {            return Mono.just(new AuthorizationDecision(false));        }        String realToken = token.trim().substring(7);        Long ttl = stringRedisTemplate.getExpire(RedisConstants.ONLINE_TOKEN_PREFIX_KV + realToken);        if (ttl <= 0) {            return Mono.just(new AuthorizationDecision(false));        }        //  获取访问资源所需的角色        List<String> authorizedRoles = new ArrayList<>();   //  拥有访问权限的角色        Map<Object, Object> urlRoleMap = stringRedisTemplate.opsForHash().entries(RedisConstants.URL_ROLE_MAP_HK);        for (Map.Entry<Object, Object> entry : urlRoleMap.entrySet()) {            String permissionUrl = (String) entry.getKey();            List<String> roles = JSON.parseArray((String) entry.getValue(), String.class);            if (pathMatcher.match(permissionUrl, path)) {                authorizedRoles.addAll(roles);            }        }        //  没有配置权限规则表示无需授权,直接放行        if (CollectionUtils.isEmpty(authorizedRoles)) {            return Mono.just(new AuthorizationDecision(true));        }        //  判断用户拥有的角色是否可以访问资源        return authentication.filter(Authentication::isAuthenticated)                .flatMapIterable(Authentication::getAuthorities)                .map(GrantedAuthority::getAuthority).any(authorizedRoles::contains)                .map(AuthorizationDecision::new)                .defaultIfEmpty(new AuthorizationDecision(false));    }}

菜单权限在Redis中是这样存储的 url -> [角色编码, 角色编码, 角色编码]

查询SQL

SELECT	t1.url,	t3.`code` AS role_code FROM	sys_menu t1	LEFT JOIN sys_role_menu t2 ON t1.id = t2.menu_id	LEFT JOIN sys_role t3 ON t2.role_id = t3.idWHERE t1.url is NOT NULL;

存储到Redis

HSET "/soas-order/order/pageList" "[\"admin\",\"org\"]"HSET "/soas-order/order/save" "[\"admin\",\"enterprise\"]"
资源访问的一些配置

ResourceServerConfig

package com.soa.supervision.gateway.config;import cn.hutool.core.codec.Base64;import cn.hutool.core.io.IoUtil;import com.soa.supervision.gateway.util.ResponseUtils;import lombok.SneakyThrows;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import org.springframework.core.io.ClassPathResource;import org.springframework.core.io.Resource;import org.springframework.http.HttpStatus;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.authentication.AbstractAuthenticationToken;import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;import org.springframework.security.config.web.server.ServerHttpSecurity;import org.springframework.security.core.AuthenticationException;import org.springframework.security.oauth2.jwt.Jwt;import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;import org.springframework.security.web.server.SecurityWebFilterChain;import org.springframework.security.web.server.ServerAuthenticationEntryPoint;import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.io.InputStream;import java.security.KeyFactory;import java.security.interfaces.RSAPublicKey;import java.security.spec.X509EncodedKeySpec;/** * @Author ChengJianSheng * @Date 2022/02/15 */@Configuration@EnableWebFluxSecuritypublic class ResourceServerConfig {    @Autowired    private IgnoreUrlProperties ignoreUrlProperties;    @Autowired    private AuthorizationManager authorizationManager;    @Bean    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {        //  配置JWT解码相关        http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());//.publicKey(rsaPublicKey());        http.authorizeExchange()                .pathMatchers(ignoreUrlProperties.getUrls()).permitAll()                .anyExchange().access(authorizationManager)                .and()                .exceptionHandling()                .accessDeniedHandler(accessDeniedHandler())                .authenticationEntryPoint(authenticationEntryPoint())                .and()                .csrf().disable();        return http.build();    }    public Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter() {        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();//        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);    }    /**     * 未授权(没有访问权限)     */    public ServerAccessDeniedHandler accessDeniedHandler() {        return (ServerWebExchange exchange, AccessDeniedException denied) -> {            Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(resp -> ResponseUtils.writeErrorInfo(resp, HttpStatus.UNAUTHORIZED));            return mono;        };    }    /**     * 未登录     */    public ServerAuthenticationEntryPoint authenticationEntryPoint() {        return (ServerWebExchange exchange, AuthenticationException ex) -> {            Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(resp -> ResponseUtils.writeErrorInfo(resp, HttpStatus.FORBIDDEN));            return mono;        };    }    /**     * 测试本地公钥(可选)     */    @SneakyThrows    @Bean    public RSAPublicKey rsaPublicKey() {        Resource resource = new ClassPathResource("public.key");        InputStream is = resource.getInputStream();        String publicKeyData = IoUtil.read(is).toString();        X509EncodedKeySpec keySpec = new X509EncodedKeySpec((Base64.decode(publicKeyData)));        KeyFactory keyFactory = KeyFactory.getInstance("RSA");        RSAPublicKey rsaPublicKey = (RSAPublicKey)keyFactory.generatePublic(keySpec);        return rsaPublicKey;    }}

说明:

公钥可以从远程获取,也可以放在本地从本地读取。上面代码中,被注释调的就是测试一下从本地读取公钥。

从源码中我们也可以看出有多种方式,本例中采用的是从远程获取,因此在前面application.yml中配置了spring.security.oauth2.resourceserver.jwt.jwk-set-uri

响应工具类ResponseUtils

package com.soa.supervision.gateway.util;import com.alibaba.fastjson.JSON;import com.tgf.common.domain.RespResult;import com.tgf.common.util.RespUtils;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.server.reactive.ServerHttpResponse;import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;/** * @Author ChengJianSheng * @Date 2022/2/16 */public class ResponseUtils {    public static Mono<Void> writeErrorInfo(ServerHttpResponse response, HttpStatus httpStatus) {        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);        response.getHeaders().set("Access-Control-Allow-Origin", "*");        response.getHeaders().set("Cache-Control", "no-cache");        RespResult respResult = RespUtils.fail(httpStatus.value(), httpStatus.getReasonPhrase());        String body = JSON.toJSONString(respResult);        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));        return response.writeWith(Mono.just(buffer))                .doOnError(error -> DataBufferUtils.release(buffer));    }}

鉴权通过以后,可以解析token,并将一些有用的信息放到header中传给下游的业务服务,这样的话业务服务就无需再解析token了,在网关这里统一处理是最适合的了 TokenFilter

package com.soa.supervision.gateway.filter;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.nimbusds.jose.JWSObject;import com.soa.supervision.gateway.constant.AuthConstants;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.text.ParseException;/** * 只有当请求URL匹配路由规则时才会执行全局过滤器 * * @Author ChengJianSheng * @Date 2021/12/15 */@Slf4j@Componentpublic class TokenFilter implements GlobalFilter {    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        ServerHttpRequest request = exchange.getRequest();        String token = request.getHeaders().getFirst(AuthConstants.JWT_TOKEN_HEADER);        if (StringUtils.isBlank(token)) {            return chain.filter(exchange);        }        String realToken = token.trim().substring(7);        try {            JWSObject jwsObject = JWSObject.parse(realToken);            String payload = jwsObject.getPayload().toString();            JSONObject jsonObject = JSON.parseObject(payload);            String userId = jsonObject.getString("userId");            String deptId = jsonObject.getString("deptId");            request = request.mutate()                    .header(AuthConstants.HEADER_USER_ID, userId)                    .header(AuthConstants.HEADER_DEPT_ID, deptId)                    .build();            //  可以把整个Payload放到请求头中//            exchange.getRequest().mutate().header("user", payload).build();            exchange = exchange.mutate().request(request).build();        } catch (ParseException e) {            log.error("解析token失败!原因: {}", e.getMessage(), e);        }        return chain.filter(exchange);    }}

最后,是几个常量类 AuthConstants

package com.soa.supervision.gateway.constant;/** * @Author ChengJianSheng * @Date 2021/11/17 */public class AuthConstants {    public static final String ROLE_PREFIX = "ROLE_";    public static final String JWT_TOKEN_HEADER = "Authorization";    public static final String JWT_TOKEN_PREFIX = "Bearer ";    public static final String TOKEN_WHITELIST_PREFIX = "TOKEN:";    public static final String HEADER_USER_ID = "x-user-id";    public static final String HEADER_DEPT_ID = "x-dept-id";}

RedisConstants

package com.soa.supervision.gateway.constant;/** * @Author ChengJianSheng * @Date 2022/2/16 */public class RedisConstants {    //  资源角色映射关系    public static final String URL_ROLE_MAP_HK = "URL_ROLE_HS";    //  有效的TOKEN    public static final String ONLINE_TOKEN_PREFIX_KV = "ONLINE_TOKEN:";}

最后,数据库脚本

DROP TABLE IF EXISTS `sys_menu`;CREATE TABLE `sys_menu`  (  `id` int(11) NOT NULL AUTO_INCREMENT,  `system_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '系统名称',  `system_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '系统编码',  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',  `parent_id` int(11) NOT NULL COMMENT '父级菜单ID',  `route_path` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',  `component` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件',  `icon` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',  `sort` smallint(8) NOT NULL COMMENT '排序号',  `hidden` tinyint(4) NOT NULL COMMENT '是否隐藏(1:是,0:否)',  `create_time` datetime NOT NULL COMMENT '创建时间',  `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',  `create_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建人',  `update_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '修改人',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单表' ROW_FORMAT = DYNAMIC;DROP TABLE IF EXISTS `sys_permission`;CREATE TABLE `sys_permission`  (  `id` int(11) NOT NULL AUTO_INCREMENT,  `menu_id` int(11) NOT NULL COMMENT '菜单ID',  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称',  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'URL',  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;DROP TABLE IF EXISTS `sys_role`;CREATE TABLE `sys_role`  (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',  `code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色编码',  `sort` smallint(8) NOT NULL COMMENT '排序号',  `create_time` datetime NOT NULL COMMENT '创建时间',  `update_time` datetime NOT NULL COMMENT '修改时间',  `create_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',  `update_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改人',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;DROP TABLE IF EXISTS `sys_role_menu`;CREATE TABLE `sys_role_menu`  (  `id` int(11) NOT NULL AUTO_INCREMENT,  `role_id` int(11) NOT NULL COMMENT '角色ID',  `menu_id` int(11) NOT NULL COMMENT '菜单ID',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色菜单表' ROW_FORMAT = DYNAMIC;

项目截图

原文链接:

标签: #java登录接口