一、搭建认证服务器
新建一个springboot2项目,加入以下两个配置类就可以启动服务器
1. AuthorizationServerConfiguration
主要有3个配置方法
(1)这边取消两个api的访问限制,以及允许将客户端的id、密码直接写入form表单
-- 客户端不是用户,是指第三方网站。代码中用client来代表客户端
(2)第二个方法,用来指定客户端的信息
(3)第三个方法用来配置token的存储位置
2. WebSecurityConfiguration
主要是用来提供三个bean
这样就可以了,用以下两个api来测试
(1)http://localhost:37001/oauth/token
(2)http://localhost:37001/oauth/check_token
(代码)蓝奏云:https://wws.lanzous.com/iG4xHlo426h
有几个地方可以再改
(数据库)蓝奏云:https://wws.lanzous.com/ibQBPlo420b
(1)客户端信息存储到数据库
(2)token存储到redis
(3)用户信息存储到数据库
输入用户名,返回UserDetails接口的实现类
二、修改用户密码加密
(1)一种方法是使用自定义的PasswordEncoder
(2)另一种方法是覆写用户登录认证的逻辑
这里使用md5加盐加密
package hlmio.oauth2.conf.security.login;
import hlmio.oauth2.model.User;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 登录认证的Provider,自定义实现{@link AuthenticationProvider} <br>
*/
@Log
@Component
public class LoginAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsServiceImpl;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// http请求的账户密码
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// 根据用户名查询数据库
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username);
log.info(String.format("[http请求的账户密码]: %s/%s", username, password));
if (userDetails == null) {
throw new BadCredentialsException("用户名未找到");
}
String rawPassword = password + ((User)userDetails).getSalt();
String encodedPassword = "{MD5}"+userDetails.getPassword();
if(!passwordEncoder.matches(rawPassword,encodedPassword)){
throw new BadCredentialsException("密码不正确");
}
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}
/**
* 是否支持处理当前Authentication对象类型
*/
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
三、修改用户权限认证
主要是重写 accessDecisionManager 里面的权限判断逻辑
(1)MethodSecurityMetadataSource 用来读取方法注解上写的权限名
package hlmio.oauth2.conf.security.access;
import hlmio.oauth2.conf.security.other.NoAccess;
import hlmio.oauth2.conf.security.other.NoAuth;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@Component
public class MyMethodSecurityMetadataSource extends AbstractMethodSecurityMetadataSource {
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return Collections.emptyList();
}
if(targetClass.getName().indexOf("hlmio.oauth2.api") == -1){
return Collections.emptyList();
}
System.out.println("method: '" + method.getName() + "' class: '" + targetClass + "'");
ArrayList<ConfigAttribute> attrs = new ArrayList(5);
// region 权限(1):控制器名::方法名
// 获取控制器的名称
String controllerName_all = targetClass.getName();
String[] controllerName_arr = controllerName_all.split("\\.");
String controllerName = controllerName_arr[controllerName_arr.length-1];
// 获取方法名
String funcName = method.getName();
// 权限字段的名称
String permName = controllerName + "::" + funcName;
System.out.println(permName);
attrs.add(new MyConfigAttribute(permName));
// endregion
// region 权限(2):注解NoAuth
NoAuth noAuth = (NoAuth)this.findAnnotation(method, targetClass, NoAuth.class);
NoAccess noAccess = (NoAccess)this.findAnnotation(method, targetClass, NoAccess.class);
if(noAuth!=null){
attrs.add(new MyConfigAttribute("NoAuth"));
}
if(noAccess!=null){
attrs.add(new MyConfigAttribute("NoAccess"));
}
// endregion
attrs.trimToSize();
return attrs;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
private <A extends Annotation> A findAnnotation(Method method, Class<?> targetClass, Class<A> annotationClass) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
A annotation = AnnotationUtils.findAnnotation(specificMethod, annotationClass);
if (annotation != null) {
this.logger.debug(annotation + " found on specific method: " + specificMethod);
return annotation;
} else {
if (specificMethod != method) {
annotation = AnnotationUtils.findAnnotation(method, annotationClass);
if (annotation != null) {
this.logger.debug(annotation + " found on: " + method);
return annotation;
}
}
annotation = AnnotationUtils.findAnnotation(specificMethod.getDeclaringClass(), annotationClass);
if (annotation != null) {
this.logger.debug(annotation + " found on: " + specificMethod.getDeclaringClass().getName());
return annotation;
} else {
return null;
}
}
}
}
框架要的权限项类
package hlmio.oauth2.conf.security.access;
import lombok.Setter;
import org.springframework.security.access.ConfigAttribute;
public class MyConfigAttribute implements ConfigAttribute {
@Setter
String attribute;
MyConfigAttribute(){
}
MyConfigAttribute(String attribute){
this.attribute = attribute;
}
@Override
public String getAttribute(){
return attribute;
}
}
(2)AccessDecisionManager 里面写权限判定的逻辑
package hlmio.oauth2.conf.security.access;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
List<String> targetAccessList = configAttributes.stream()
.map( i -> i.getAttribute())
.collect(Collectors.toList());
// 放行无需登录和权限的注解
if(targetAccessList.contains("NoAuth")){
return;
}
// 拒绝未登录用户
if (authentication == null || "anonymousUser".equals(authentication.getName())) {
throw new AccessDeniedException("当前访问需要登录");
}
// 放行无需权限的注解
if(targetAccessList.contains("NoAccess")){
return;
}
List<String> userAccessList = authentication.getAuthorities().stream()
.map( i -> i.getAuthority())
.collect(Collectors.toList());
for (String s : userAccessList) {
for (String s1 : targetAccessList) {
if(s != null){
if(s.equals(s1)){
return;
}
}
}
}
throw new AccessDeniedException("当前访问没有权限");
}
public boolean supports(ConfigAttribute attribute) {
return true;
}
public boolean supports(Class<?> clazz) {
return MethodInvocation.class.isAssignableFrom(clazz);
}
}
github:https://github.com/hl-mio/a1