前一篇文件已经实现了基于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_validity | access_token有效时长 |
refresh_token_validity | refresh_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:
获取设备信息:
至此接口调用成功。