【基于Oauth2的api接口开发】2、配置从数据库中获取

        前一篇文件已经实现了基于Oauth2的接口开发,但是相关的配置都是由代码写死,变更起来比较麻烦,所以本文实现从数据库中读取配置,实现功能。

导入MySQL脚本

DROP TABLE IF EXISTS `clientdetails`;
CREATE TABLE `clientdetails`  (
  `appId` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resourceIds` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `appSecret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `grantTypes` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `redirectUrl` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `autoApproveScopes` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`appId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_approvals
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals`  (
  `userId` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `clientId` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `expiresAt` timestamp(0) DEFAULT NULL,
  `lastModifiedAt` timestamp(0) DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_client_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code`  (
  `code` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authentication` blob
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_password
-- ----------------------------
DROP TABLE IF EXISTS `t_password`;
CREATE TABLE `t_password`  (
  `account_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'ID',
  `account_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户名',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码',
  `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '帐号启用状态:0->禁用;1->启用',
  `create_time` datetime(0) DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`account_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

配置授权服务器

/**
 * 资源认证配置
 *
 * [/oauth/authorize] [/oauth/token] [/oauth/check_token]
 * [/oauth/confirm_access] [/oauth/token_key] [/oauth/error]
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    /**
     * 注入用于支持 password 模式
     */
    @Resource
    private AuthenticationManager authenticationManager;
    /**
     * 需要加上,避免循环依赖问题
     */
    @Lazy
    @Resource(name = "userDetailsServiceBean")
    private UserDetailsService userDetailsService;

    @Resource
    private DataSource dataSource;

    @Resource
    private WebResponseExceptionTranslator customOAuth2ResponseExceptionTranslator;

    /**
     * 设置用户密码的加密方式
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * Token 持久化
     *
     * @return TokenStore
     */
    @Bean
    public TokenStore tokenStore() {
        // 基于 JDBC 实现,令牌保存到数据库
        return new JdbcTokenStore(dataSource);
    }

    /**
     * A service that provides the details about an OAuth2 client.
     *
     * @return ClientDetailsService
     * <p>
     * 基于 JDBC 实现,需要事先在数据库配置客户端信息
     */
    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * Authorization Server endpoints.
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore())
                .exceptionTranslator(customOAuth2ResponseExceptionTranslator);

        // 用于支持密码模式
        endpoints.authenticationManager(authenticationManager);
    }

    /**
     * 授权服务安全配置
     *
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer)
            throws Exception {
        oauthServer.passwordEncoder(passwordEncoder());

        /**
         * 对于端点可以匿名访问
         * [/oauth/authorize] [/oauth/token] [/oauth/check_token]
         * [/oauth/confirm_access] [/oauth/token_key] [/oauth/error]
         */
        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }
    /**
     * 授权客户端配置
     *
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 客户端配置
        clients.withClientDetails(jdbcClientDetailsService());
    }
}

配置资源服务器

/**
 * ResourceServer配置
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    /**
     * 保存匿名访问的 url,表示可以不需要权限直接可以访问
     */
    private final List<String> anonymousAntPatterns = new ArrayList<>();

    @Resource
    private MyAccessDeniedHandler myAccessDeniedHandler;

    @Resource
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
    /**
     * 需要加上,避免循环依赖问题
     */
    @Lazy
    @Resource
    private TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/v1/**").permitAll();

//        http.requestMatchers().antMatchers("/v1/**").and().authorizeRequests().antMatchers("/v1/**").authenticated()
//        .and().csrf().disable();

        // 对登录注册要允许匿名访问
        for (String pattern : anonymousAntPatterns) {
            http.authorizeRequests().antMatchers(pattern).permitAll();
        }

        // 禁用 session
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().anyRequest().authenticated().and()
                .cors().and()
                .csrf().disable();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 配置资源 ID,需要在 oauth_client_details 表中添加,详细查看数据库脚本
        resources.resourceId("app-resources").stateless(true);
        resources.accessDeniedHandler(myAccessDeniedHandler);
        resources.authenticationEntryPoint(myAuthenticationEntryPoint);
    }
}

配置安全

/**
 * WebSecurity 配置文件
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private LoginAuthenticationProvider loginAuthenticationProvider;

    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return new UserDetailsServiceImpl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(loginAuthenticationProvider);
        auth.userDetailsService(userDetailsServiceBean());
    }

    /**
     * 用于支持 password 模式 密码模式需要 AuthenticationManager 支持
     * password 模式一点要加上这个
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http   // 配置登录页并允许访问
                //.formLogin().permitAll()
                // 配置Basic登录
                //.and().httpBasic()
                // 配置登出页面
                .logout().logoutUrl("/logout").logoutSuccessUrl("/")
                // 配置允许访问的链接
                .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**","/v1/**").permitAll()
                // 其余所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                // 关闭跨域保护;
                .and().csrf().disable();
    }
}

自定义授权失败或错误 

@Component
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Resource private ObjectMapper objectMapper;

    @Override public void onAuthenticationFailure(HttpServletRequest request,
                                                  HttpServletResponse response, AuthenticationException e)
            throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ServletOutputStream writer = response.getOutputStream();
        Integer code = HttpServletResponse.SC_UNAUTHORIZED;
        String message = e.getMessage();
        if (e instanceof BusinessException) {
            BusinessException be = (BusinessException) e;
            code = be.getCode();
            message = be.getMessage();
        }
        if (e instanceof DisabledException) {
            code = ResultEnum.ACCOUNT_DISABLED.getCode();
            message = ResultEnum.ACCOUNT_DISABLED.getName();
        }
        log.info("MyAuthenticationFailureHandler 用户登录失败 [{}] [{}]", code, message);
        objectMapper.writeValue(writer, Result.fail(code, message));
    }
}

自定义授权错误传输器

@Slf4j
@Component
public class AuthenticationResponseExceptionTranslator
        implements WebResponseExceptionTranslator {

    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        log.error("MyAuthenticationResponseExceptionTranslator :: {} ", e.getMessage());

        if (e instanceof InvalidGrantException) {
            return ResponseEntity
                    .status(HttpStatus.BAD_REQUEST)
                    .body(new BusinessAuthenticationException(ResultEnum.ACCOUNT_OR_PASSWORD_ERROR));
        }

        if (e instanceof InvalidTokenException) {
            return ResponseEntity
                    .status(HttpStatus.UNAUTHORIZED)
                    .body(new BusinessAuthenticationException(ResultEnum.TOKEN_INVALID));
        }

        if (e instanceof InternalAuthenticationServiceException ||
                e instanceof InvalidRequestException ||
                e instanceof HttpRequestMethodNotSupportedException) {
            return ResponseEntity
                    .status(HttpStatus.BAD_REQUEST)
                    .body(new BusinessAuthenticationException(e.getMessage(),
                            ResultEnum.AUTH_SERVER_ERROR.getCode()));
        }

        if (e instanceof BusinessException) {
            BusinessException ex = (BusinessException) e;
            return ResponseEntity
                    .status(HttpStatus.BAD_REQUEST)
                    .body(new BusinessAuthenticationException(ex.getMessage(), ex.getCode()));
        }

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new BusinessAuthenticationException(ResultEnum.UN_KNOWN_ERROR));
    }
}

自定义授权错误解析器

public class MyAuthenticationExceptionSerializer
        extends StdSerializer<BusinessAuthenticationException> {
    protected MyAuthenticationExceptionSerializer() {
        super(BusinessAuthenticationException.class);
    }

    @Override public void serialize(BusinessAuthenticationException e, JsonGenerator jsonGenerator,
                                    SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeNumberField("code", e.getCode());
        jsonGenerator.writeStringField("message", e.getMessage());
        if (e.getAdditionalInformation() != null) {
            for (Map.Entry<String, String> entry : e.getAdditionalInformation().entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                jsonGenerator.writeStringField(key, value);
            }
        }
        jsonGenerator.writeEndObject();
    }
}

解决认证过的用户访问无权限资源时的异常

@Component
@Slf4j
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Resource private ObjectMapper objectMapper;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException e) throws
            IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = response.getWriter();
        log.info("MyAccessDeniedHandler 用户无权访问 [{}]", e.getMessage());
        objectMapper.writeValue(writer, Result.fail(ResultEnum.PERMISSION_ACCESS_DENIED));
    }
}

解决匿名用户访问无权限资源时的异常

@Component
@Slf4j
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Resource private ObjectMapper objectMapper;

    @Override public void commence(HttpServletRequest request,
                                   HttpServletResponse response, AuthenticationException e)
            throws IOException, ServletException {
        log.error("MyAuthenticationEntryPoint :: {} {} ", e.getMessage(), request.getRequestURL());
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ServletOutputStream writer = response.getOutputStream();
        String message = e.getMessage();
        Integer code = HttpServletResponse.SC_UNAUTHORIZED;
        if (e instanceof BusinessException) {
            BusinessException be = (BusinessException) e;
            message = be.getMessage();
            code = be.getCode();
        }
        if (e instanceof InsufficientAuthenticationException) {
            message = ResultEnum.NOT_LOGIN.getName();

            Throwable cause = e.getCause();
            if (cause instanceof InvalidTokenException) {
                message = ResultEnum.TOKEN_INVALID.getName();
                code = ResultEnum.TOKEN_INVALID.getCode();
            }
        }
        objectMapper.writeValue(writer, Result.fail(code, message));
    }
}

构建用户和权限信息

public class MyUserDetails implements UserDetails {

    public static final String CAN_USE = "1";

    private PasswordDTO passwordDTO;

    /**
     * 权限集合
     */
    private List<GrantedAuthority> authorities;

    public MyUserDetails() {
    }

    public MyUserDetails(PasswordDTO passwordDTO,
                         List<GrantedAuthority> authorities) {
        this.passwordDTO = passwordDTO;
        this.authorities = authorities;
    }

    @Override public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

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

    @Override public String getUsername() {
        return passwordDTO.getUserName();
    }

    @Override public boolean isAccountNonExpired() {
        return Boolean.TRUE;
    }

    @Override public boolean isAccountNonLocked() {
        return Boolean.TRUE;
    }

    @Override public boolean isCredentialsNonExpired() {
        return Boolean.TRUE;
    }

    @Override public boolean isEnabled() {
        return StringUtils.equals(passwordDTO.getStatus(), CAN_USE);
    }
}

自定义认证 UserDetailsService

  • 需要继承 UserDetailsService 类
  • 重写 loadUserByUsername 方法,通过这个方法得到访问接口传递的用户名
  • 根据用户名进行用户名密码校验和权限校验
  • 查询出来的用户信息构建 UserDetails 对应进行返回传递
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordService passwordService;

    /**
     * Spring Security
     *
     * @param accountName 账号
     * @return 是否成功
     * @throws UsernameNotFoundException 用户名密码异常
     */

    @Override
    public MyUserDetails loadUserByUsername(String accountName)
            throws UsernameNotFoundException {

        if (StringUtils.isEmpty(accountName)) {
            throw new BusinessException(ResultEnum.ACCOUNT_IS_EMPTY);
        }


        // TODO: 禁用账号如何防止多次请求,访问数据库 ???
        PasswordDTO passwordDTO = passwordService.getPassword(accountName);
        if (ObjectUtils.isEmpty(passwordDTO)) {
            throw new BusinessException(ResultEnum.ACCOUNT_OR_PASSWORD_ERROR);
        }

        // TODO: 角色、权限集合
        List<GrantedAuthority> grantedAuthorities = new ArrayList();
        grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
        return new MyUserDetails(passwordDTO, grantedAuthorities);
    }
}

查询数据库操作

service层

@Service
public class PasswordServiceImpl implements PasswordService {

    @Autowired
    private UserDao userDao;

    @Override
    public PasswordDTO getPassword(String accountName) {
        return userDao.getPassword( accountName );
    }
}

实体PasswordDTO

@Data
public class PasswordDTO extends BaseDTO implements Serializable {

    private static final long serialVersionUID = 2459582478102836423L;

    private Long userId;

    private String userName;

    private String password;

    private String status;
}
@Data
public class BaseDTO {

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 修改时间
     */
    private LocalDateTime updateTime;
}

mapper语句

<select id="getPassword" parameterType="java.lang.String" resultType="com.cn.sys.pojo.PasswordDTO">
    select t.account_id as userId, t.account_name as userName, t.password,t.status
    from t_password t where t.account_name = #{accountName, jdbcType=VARCHAR}
</select>

数据库表添加数据

-- ----------------------------
-- Records of t_password
-- ----------------------------
INSERT INTO `t_password` VALUES ('785919644115668992', 'sed', '$2a$10$gVIu.4qQynQIYpzyzIgu3ubEk6Z5NSEx5LZ/0fRfilsVPGA4EjKTm', '1', '2022-03-07 18:17:50', '2022-03-07 18:17:52');

-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('client', 'app-resources', '$2a$10$LlvdWDFX1sDkL8TRPvbRJugefVhyaX1z7Oz.b/StPoBjQLrBNGXb2', 'app', 'password,refresh_token,authorization_code,client_credentials,implicit', NULL, NULL, 1200, 50000, NULL, NULL);

其中表【oauth_client_details】中的字段对应关系如下表所示:

字段含义
client_id相当于主键ID,区分用户给予的授权
resource_ids配置资源ID,要与资源服务器配置中的值相同
clinet_secret用于确认你使用的client_id确实是你这个网站所持有的,相当于证书,需要加密,加密方法为BCryptPasswordEncoder()
scope授权范围
authorized_grant_types授权模式,比如password表示密码模式;authorization_code表示授权码模式
access_token_validityaccess_token有效时长
refresh_token_validityrefresh_token有效时长

另外表【t_password】中的密码同样要使用BCryptPasswordEncoder方式加密,表中的用户名与密码在获取access_token时会用到。

BCryptPasswordEncoder加密方法

public static void main(String[] args) {
    String password = "sed123";
    String pwd = encodePassword(password);
    System.out.println(pwd);
}

private static BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();

public static String encodePassword(String password){
    return bCryptPasswordEncoder.encode(password);
}

用到的工具类

Result:返回结果类

@Data
public class Result<T> {

    private Integer code;

    private T data;

    private String msg;

    public Result() {
        super();
    }

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

    public void setResult(ResultEnum resultEnum){
        this.code = resultEnum.getCode();
        this.msg = resultEnum.getName();
    }

    public void setSuccess(T data) {
        this.code = ResultEnum.SUCCESS.getCode();
        this.msg = ResultEnum.SUCCESS.getName();
        this.data = data;
    }

    public static <T> Result<T> fail(ResultEnum resultEnum) {
        return createResult(null, resultEnum.getCode(), resultEnum.getName());
    }

    public static <T> Result<T> fail(Integer code, String message) {
        return createResult(null, code, message);
    }

    private static <T> Result<T> createResult(T data, Integer code, String message) {
        Result<T> r = new Result<>();
        r.setCode(code);
        r.setData(data);
        r.setMsg(message);
        return r;
    }
}

ResultEnum:自定义错误异常码

public enum ResultEnum {

    SUCCESS(201,"请求成功"),
    UN_KNOWN_ERROR(-1, "未知错误"),

    PARAM_FORMAT_ERROR(10002,"参数格式错误"),
    LACK_ESSENTIAL_PARAM(10003,"缺少必填参数"),
    QUERY_FAIL(10004,"数据查询失败"),

    TOKEN_INVALID(10101, "invalid access token"),
    ACCOUNT_OR_PASSWORD_ERROR(10102, "账号或密码错误"),
    ACCOUNT_DISABLED(10103, "账号已禁用"),
    ACCOUNT_IS_EMPTY(10004, "账号不能为空"),


    NOT_LOGIN(20002, "未登录,请先登录"),
    AUTH_SERVER_ERROR(20005, "授权服务异常"),

    PERMISSION_ACCESS_DENIED(50001, "permission access denied");


    private int code;
    private String name;

    private ResultEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public int getCode() {
        return this.code;
    }

    public String getName() {
        return this.name;
    }

    public static ResultEnum getNameByCode(int code) {
        for (ResultEnum resultEnum : ResultEnum.values()) {
            if (code == resultEnum.getCode()) {
                return resultEnum;
            }
        }
        return null;
    }
}

BusinessException

public class BusinessException extends AuthenticationException {
    private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private Integer code;

    private String message;

    public BusinessException(String message) {
        super(message);
        this.message = message;
    }

    public BusinessException(String message, Integer code) {
        super(message);
        this.message = message;
        this.code = code;
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
        this.message = String.format("%s %s", message, cause.getMessage());
    }

    public BusinessException(ResultEnum resultEnum) {
        super(resultEnum.getName());
        this.message = resultEnum.getName();
        this.code = resultEnum.getCode();
    }

    public BusinessException(ResultEnum resultEnum, Object... args) {
        super(resultEnum.getName());
        String message = resultEnum.getName();
        try {
            message =
                    String.format("%s %s", resultEnum.getName(), OBJECT_MAPPER.writeValueAsString(args));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        this.message = message;
        this.code = resultEnum.getCode();
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    @Override public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

BusinessAuthenticationException

@JsonSerialize(using = MyAuthenticationExceptionSerializer.class)
public class BusinessAuthenticationException extends OAuth2Exception {
    private String message;
    private Integer code;

    public BusinessAuthenticationException(String message, Integer code) {
        super(message);
        this.message = message;
        this.code = code;
    }

    public BusinessAuthenticationException(ResultEnum resultEnum) {
        super(resultEnum.getName());
        this.message = resultEnum.getName();
        this.code = resultEnum.getCode();
    }

    @Override public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

可能出现的问题一:Bad Credentials

自定义 Spring Security的登录校验

@Component
public class LoginAuthenticationProvider extends DaoAuthenticationProvider {

    @Lazy
    @Resource(name = "userDetailsServiceBean")
    @Autowired
    private UserDetailsService jdbcUserDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private void setJdbcUserDetailsService() {
        setUserDetailsService(jdbcUserDetailsService);
    }

    @PostConstruct
    public void initProvider() {
        ReloadableResourceBundleMessageSource localMessageSource = new ReloadableResourceBundleMessageSource();
        localMessageSource.setBasenames("messages_zh_CN");
        messages = new MessageSourceAccessor(localMessageSource);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }

}

在WebSecurity 配置文件中配置我们自己的 LoginAuthenticationProvider,如下所示:

@Autowired
private LoginAuthenticationProvider loginAuthenticationProvider;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(loginAuthenticationProvider);
    auth.userDetailsService(userDetailsServiceBean());
}

可能出现的问题二:关闭csrf

报错信息:Could not verify the provided CSRF token because your session was not found.

解决:在WebSecurity 配置文件中关闭csrf即可,如下:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http   // 配置登录页并允许访问
            //.formLogin().permitAll()
            // 配置Basic登录
            //.and().httpBasic()
            // 配置登出页面
            .logout().logoutUrl("/logout").logoutSuccessUrl("/")
            // 配置允许访问的链接
            .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**","/v1/**").permitAll()
            // 其余所有请求全部需要鉴权认证
            .anyRequest().authenticated()
            // 关闭跨域保护;
            .and().csrf().disable();
}

测试接口

获取access_token:

获取设备信息:

 

至此接口调用成功。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值