前言
项目中出现的,接口权限问题,和第三方鉴权功能的简单实现。技术采用spring boot+spring security+oauth2相关技术,做一个简单的权限管理。本项目,只适合用来学习搭建项目,并不适用于直接用于项目开发。还需要加点改造。本项目github地址:
https://gitee.com/shen_wen_jia/learning-projects/tree/master/springsecurity-oauth2
整体项目架构
jar包导入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--spring security实现的相关jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- oauth2.0支持组件 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<!--模板引擎,搭建简单的登录界面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--工具包,主要用了一个JSON的功能-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
</dependencies>
1.spring secutity实现接口的权限访问控制
1)项目搭建
application.yml配置
mvc:
view:
suffix: html
prefix:
server:
port: 8080
servlet:
context-path: /oauth
使用
首先,使用spring security搭建一个简单的登陆跳转,接口权限验证功能。使用到的代码文件有SecurityConfig.java-用来做spring security的详细配置。MyUserDetailService.java-用来做登陆逻辑。LoginController.java-用来做简单的登陆界面跳转,使用th模板引擎搭建了一个简单的登陆界面。UserController.java-用来做简单的接口权限项目测试。
SecurityConfig.java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/","/login","/hello","/oauth/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login_p")
//登录处理接口
.loginProcessingUrl("/doLogin")
//定义登录时,用户名的 key,默认为 username
.usernameParameter("username")
//定义登录时,用户密码的 key,默认为 password
.passwordParameter("password")
//成功/失败登录之后的返回逻辑
.successHandler(new AuthenticationSuccessHandler() {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
System.out.println(authentication);
out.write(JSONUtil.toJsonPrettyStr(authentication));
out.flush();
}
})
.failureHandler(new AuthenticationFailureHandler(){
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(e.toString());
out.flush();
}
})
.permitAll()
.and()
//退出登录的登录接口。
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("logout success");
out.flush();
}
})
.permitAll();
}
/**
* 密码加密解密的配置
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 需要配置这个支持password模式-oauth2的相关配置
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
配置中调用了前端自己定义的登陆逻辑-index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/doLogin}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
MyUserDetailService.java:用户登陆的判断逻辑在这里实现
这里的登陆判断逻辑,最好还是根据自己的数据库设计,来加入相应的逻辑判断。
@Component
public class MyUserDetailService implements UserDetailsService {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
logger.info("登陆用户名:{}",s);
String password = "123";
password = passwordEncoder.encode(password);
List<SimpleGrantedAuthority> authorities = new ArrayList<>(8);
//这里加上用户的权限逻辑,这里我使用-来划分资源的细化权限
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("user-add");
authorities.add(authority);
//角色权限,使用角色权限时,需要在加色的前方加上ROLE_,代表一个角色权限
SimpleGrantedAuthority authority2 = new SimpleGrantedAuthority("ROLE_admin");
authorities.add(authority2);
return new User(s,password, true, true, true, true,
authorities);
}
}
UserController.java :用户的测试,使用注解管理接口的权限
@RestController
@RequestMapping("user")
public class UserController {
private Logger logger = LoggerFactory.getLogger(PermisssionController.class);
@GetMapping
//使用本注解来管理一个接口所应该拥有的权限
@PreAuthorize("hasPermission('user','add') or hasRole('admin')" )
public String get(){
logger.info("访问成功");
return "success";
}
}
MyPermissionEvaluator.java:测试所用 的接口的解析逻辑,比对接口的权限,返回boolean,是否能够调用。
@Configuration
public class MyPermissionEvaluator implements PermissionEvaluator {
private Logger logger = LoggerFactory.getLogger(MyPermissionEvaluator.class);
@Override
public boolean hasPermission(Authentication authentication, Object o, Object o1) {
boolean accessable = false;
logger.info(authentication.getPrincipal().toString());
if(authentication.getPrincipal().toString().compareToIgnoreCase("anonymousUser") != 0){
String privilege = o+"-"+o1;
for (GrantedAuthority authority : authentication.getAuthorities()) {
if(privilege.equalsIgnoreCase(authority.getAuthority())){
accessable = true;
break;
}
}
}
return accessable;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
return false;
}
}
构建结束
依靠上述的项目构建结果,可以看出,我们搭建一个简单的用户接口的权限验证框架。相关逻辑如下图所示
2)项目测试
待跟新
访问接口,自动跳转登录界面
失败返回
成功返回
访问接口:在没有登陆之前,是直接返回404的
2.spring security 实现oauth2的相关鉴权服务
1)相关配置
spring security框架提供了对oauth2的支持,这里简单实现一下,oauth2的相关。主要使用到2个配置文件OAuth2ServerConfig-提供相关的oauth2的相关配置,ResourceServerConfig-配置oauth2的资源访问接口(配置哪些接口是需要通过oauth2鉴权之后才能访问的)。PermisssionController-测试controller层
OAuth2ServerConfig
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.realm("oauth2-resources")
//code授权添加
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
//allow check token
.allowFormAuthenticationForClients();
}
/**
* 注入authenticationManager
* 来支持 password grant type
*/
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
//允许 GET、POST 请求获取 token,即访问端点:oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("demoApp")
.secret(passwordEncoder.encode("demoAppSecret"))
.redirectUris("http://baidu.com")//code授权添加
.authorizedGrantTypes("authorization_code","client_credentials", "password", "refresh_token")
.scopes("all")
.resourceIds("oauth2-resource")
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000);
}
ResourceServerConfig:配置了需要的接口服务
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/per/**")
.and()
.authorizeRequests()
.antMatchers("/per/**").authenticated();
}
}
PermisssionController:测试所用的权限层
@RestController
@RequestMapping("per")
public class PermisssionController {
private Logger logger = LoggerFactory.getLogger(PermisssionController.class);
@GetMapping
public String get(){
logger.info("访问成功");
return "success";
}
}
2)测试
第一步:获取token
密码模式
url:
http://localhost:8080/oauth/oauth/token?username=admin&password=123&grant_type=password&client_id=demoApp&client_secret=demoAppSecret
客户端模式
url:
http://localhost:8080/oauth/oauth/token?grant_type=client_credentials&client_id=demoApp&client_secret=demoAppSecret
第二步访问接口
错误情况:
正确的情况