Spring Security实现OAuth2.0授权服务 - 进阶版
《Spring Security实现OAuth2.0授权服务 - 基础版》介绍了如何使用Spring Security实现OAuth2.0授权和资源保护,但是使用的都是Spring Security默认的登录页、授权页,client和token信息也是保存在内存中的。
本文将介绍如何在Spring Security OAuth项目中自定义登录页面、自定义授权页面、数据库配置client信息、数据库保存授权码和token令牌。
一、引入依赖
需要在基础版之上引入thymeleaf、JDBC、mybatis、mysql等依赖。
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency> <!-- JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency> <!-- Mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency> <!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
二、client和token表
-- used in tests that use HSQL
create table oauth_client_details (
client_id VARCHAR(255) PRIMARY KEY,
resource_ids VARCHAR(255),
client_secret VARCHAR(255),
scope VARCHAR(255),
authorized_grant_types VARCHAR(255),
web_server_redirect_uri VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information TEXT(4096),
autoapprove VARCHAR(255)
); create table oauth_client_token (
token_id VARCHAR(255),
token BLOB,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255)
); create table oauth_access_token (
token_id VARCHAR(255),
token BLOB,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255),
authentication BLOB,
refresh_token VARCHAR(255)
); create table oauth_refresh_token (
token_id VARCHAR(255),
token BLOB,
authentication BLOB
); create table oauth_code (
code VARCHAR(255), authentication BLOB
); create table oauth_approvals (
userId VARCHAR(255),
clientId VARCHAR(255),
scope VARCHAR(255),
status VARCHAR(10),
expiresAt TIMESTAMP,
lastModifiedAt TIMESTAMP
); -- customized oauth_client_details table
create table ClientDetails (
appId VARCHAR(255) PRIMARY KEY,
resourceIds VARCHAR(255),
appSecret VARCHAR(255),
scope VARCHAR(255),
grantTypes VARCHAR(255),
redirectUrl VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additionalInformation VARCHAR(4096),
autoApproveScopes VARCHAR(255)
);
在oauth_client_details表添加数据:
INSERT INTO `oauth_client_details` VALUES ('net5ijy', NULL, '', 'all,read,write', 'authorization_code,refresh_token,password', NULL, 'ROLE_TRUSTED_CLIENT', 7200, 7200, NULL, NULL);
INSERT INTO `oauth_client_details` VALUES ('tencent', NULL, '', 'all,read,write', 'authorization_code,refresh_code', NULL, 'ROLE_TRUSTED_CLIENT', 3600, 3600, NULL, NULL);
三、用户、角色表
CREATE TABLE `springcloud_user` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`create_time` datetime NOT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=1; CREATE TABLE `springcloud_role` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=1; CREATE TABLE `springcloud_user_role` (
`user_id` int(11) NOT NULL ,
`role_id` int(11) NOT NULL ,
FOREIGN KEY (`role_id`) REFERENCES `springcloud_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
FOREIGN KEY (`user_id`) REFERENCES `springcloud_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
INDEX `user_id_fk` USING BTREE (`user_id`) ,
INDEX `role_id_fk` USING BTREE (`role_id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci;
用户表添加数据
INSERT INTO `springcloud_user` VALUES (1, 'admin001', '$2a$10$sXHKvdufrEfE2900ME40nOSBmeHRRUOF71szu22uaqqL8FIJeJDYW', '', '13622114309@189.cn', '2019-4-7 09:31:07');
INSERT INTO `springcloud_user` VALUES (2, 'admin002', '$2a$10$sXHKvdufrEfE2900ME40nOSBmeHRRUOF71szu22uaqqL8FIJeJDYW', '', '17809837654@189.cn', '2019-4-7 09:33:00');
角色表添加数据
INSERT INTO `springcloud_role` VALUES (1, 'ADMIN');
INSERT INTO `springcloud_role` VALUES (2, 'DBA');
INSERT INTO `springcloud_role` VALUES (3, 'USER');
用户角色关系表添加数据
INSERT INTO `springcloud_user_role` VALUES (1, 1);
INSERT INTO `springcloud_user_role` VALUES (2, 1);
四、实体类和工具类
1、User实体类
封装授权服务器登录用户信息
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private String phone;
private String email;
private Set<Role> roles = new HashSet<Role>();
private Date createTime;
// getter & setter
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password="
+ password + ", phone=" + phone + ", email=" + email
+ ", roles=" + roles + ", createTime=" + createTime + "]";
}
}
2、Role实体类
封装角色信息
public class Role implements Serializable {
private Integer id;
private String name;
public Role() {
super();
}
public Role(String name) {
super();
this.name = name;
}
// getter & setter
@Override
public String toString() {
return "Role [id=" + id + ", name=" + name + "]";
}
}
3、ResponseMessage工具类
封装接口响应信息
public class ResponseMessage {
private Integer code;
private String message;
public ResponseMessage() {
super();
}
public ResponseMessage(Integer code, String message) {
super();
this.code = code;
this.message = message;
}
// getter & setter
public static ResponseMessage success() {
return new ResponseMessage(0, "操作成功");
}
public static ResponseMessage fail() {
return new ResponseMessage(99, "操作失败");
}
}
五、DAO和Service编写
1、数据源配置
在application.properties文件配置datasource
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=system
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.dbcp2.initial-size=5
spring.datasource.dbcp2.max-active=25
spring.datasource.dbcp2.max-idle=10
spring.datasource.dbcp2.min-idle=5
spring.datasource.dbcp2.max-wait-millis=10000
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.connection-properties=characterEncoding=utf8
使用dbcp2数据源
2、mapper.xml
在src/main/resources下创建org.net5ijy.oauth2.mapper包,创建user-mapper.xml配置文件
<mapper namespace="org.net5ijy.oauth2.repository.UserRepository">
<resultMap type="User" id="UserResultMap">
<result column="id" property="id" jdbcType="INTEGER" javaType="int" />
<result column="username" property="username" jdbcType="VARCHAR"
javaType="string" />
<result column="password" property="password" jdbcType="VARCHAR"
javaType="string" />
<result column="phone" property="phone" jdbcType="VARCHAR"
javaType="string" />
<result column="email" property="email" jdbcType="VARCHAR"
javaType="string" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"
javaType="java.util.Date" />
<collection property="roles" select="selectRolesByUserId"
column="id"></collection>
</resultMap>
<!-- 根据用户名查询用户 -->
<select id="findByUsername" parameterType="java.lang.String"
resultMap="UserResultMap">
<![CDATA[
select * from springcloud_user where username = #{username}
]]>
</select>
<!-- 根据user id查询用户拥有的role -->
<select id="selectRolesByUserId" parameterType="java.lang.Integer"
resultType="Role">
<![CDATA[
select r.id, r.name from springcloud_user_role ur, springcloud_role r
where ur.role_id = r.id and ur.user_id = #{id}
]]>
</select>
</mapper>
因为我们的例子只使用了findByUsername功能,所以只写这个sql就可以了
3、DAO接口
public interface UserRepository {
User findByUsername(String username);
}
4、UserService
接口
public interface UserService {
User getUser(String username);
}
实现类
@Service
public class UserServiceImpl implements UserService { static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); @Autowired
private UserRepository userRepository; @Autowired
private JdbcTemplate jdbcTemplate; @Override
public User getUser(String username) {
return userRepository.findByUsername(username);
}
}
5、UserDetailsService实现类
这个接口的实现类需要在Security中配置,Security会使用这个类根据用户名查询用户信息,然后进行用户名、密码的验证。主要就是实现loadUserByUsername方法:
@Service
public class UserDetailsServiceImpl implements UserDetailsService { @Autowired
private UserService userService; @Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException { User user = userService.getUser(username);
if (user == null || user.getId() < 1) {
throw new UsernameNotFoundException("Username not found: "
+ username);
} return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), true, true, true, true,
getGrantedAuthorities(user));
} private Collection<? extends GrantedAuthority> getGrantedAuthorities(
User user) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (Role role : user.getRoles()) {
authorities
.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
return authorities;
}
}
六、自定义登录页面
1、controller编写
编写LoginController类,添加login方法
@RestController
public class LoginController { @GetMapping("/login")
public ModelAndView login() {
return new ModelAndView("login");
} @GetMapping("/login-error")
public ModelAndView loginError(HttpServletRequest request, Model model) {
model.addAttribute("loginError", true);
model.addAttribute("errorMsg", "登陆失败,账号或者密码错误!");
return new ModelAndView("login", "userModel", model);
}
}
2、页面代码
页面代码使用到了thymeleaf、bootstrap、表单验证等,具体的js、css引入就不赘述了,只记录最主要的内容:
<div>
<form th:action="@{/login}" method="post">
<div>
<label>用 户 名: </label>
<div>
<input name="username" />
</div>
</div>
<div>
<label>密 码: </label>
<div>
<input type="password" name="password" />
</div>
</div>
<div>
<div>
<button type="submit"> 登 陆 </button>
</div>
</div>
</form>
</div>
七、自定义授权页面
1、controller编写
编写GrantController类,添加getAccessConfirmation方法
@Controller
@SessionAttributes("authorizationRequest")
public class GrantController { @RequestMapping("/oauth/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model,
HttpServletRequest request) throws Exception { AuthorizationRequest authorizationRequest = (AuthorizationRequest) model
.get("authorizationRequest"); ModelAndView view = new ModelAndView("base-grant");
view.addObject("clientId", authorizationRequest.getClientId()); return view;
}
}
此处获取到申请授权的clientid用于在页面展示
2、页面代码
此处只写最主要的部分
<div>
<div>
<div>OAUTH-BOOT 授权</div>
<div>
<a href="javascript:;">帮助</a>
</div>
</div>
<h3 th:text="${clientId}+' 请求授权,该应用将获取您的以下信息'"></h3>
<p>昵称,头像和性别</p>
授权后表明您已同意 <a href="javascript:;" style="color: #E9686B">OAUTH-BOOT 服务协议</a>
<form method="post" action="/oauth/authorize">
<input type="hidden" name="user_oauth_approval" value="true" />
<input type="hidden" name="scope.all" value="true" />
<br />
<button class="btn" type="submit">同意/授权</button>
</form>
</div>
八、配置类和application.properties配置
1、配置mybatis
配置SqlSessionFactoryBean
- 设置数据源
- 设置包别名
- 设置mapper映射文件所在的包
@Configuration
public class MyBatisConfiguration { @Bean
@Autowired
@ConditionalOnMissingBean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource)
throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 设置数据源
sqlSessionFactoryBean.setDataSource(dataSource); // 设置别名包
sqlSessionFactoryBean.setTypeAliasesPackage("org.net5ijy.oauth2.bean"); // 设置mapper映射文件所在的包
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
String packageSearchPath = "classpath*:org/net5ijy/oauth2/mapper/**.xml";
sqlSessionFactoryBean
.setMapperLocations(pathMatchingResourcePatternResolver
.getResources(packageSearchPath)); return sqlSessionFactoryBean;
}
}
2、配置AuthorizationServerConfigurer
- 配置使用数据库保存cient信息
- 配置使用数据库保存token令牌
- 配置使用数据库保存授权码
@Configuration
public class Oauth2AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter { @Autowired
private UserDetailsService userDetailsService; @Autowired
private AuthenticationManager authenticationManager; @Autowired
private DataSource dataSource; @Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception { // 数据库管理client
clients.withClientDetails(new JdbcClientDetailsService(dataSource));
} @Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception { // 用户信息查询服务
endpoints.userDetailsService(userDetailsService); // 数据库管理access_token和refresh_token
TokenStore tokenStore = new JdbcTokenStore(dataSource); endpoints.tokenStore(tokenStore); ClientDetailsService clientService = new JdbcClientDetailsService(
dataSource); DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore);
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientService);
// tokenServices.setAccessTokenValiditySeconds(180);
// tokenServices.setRefreshTokenValiditySeconds(180); endpoints.tokenServices(tokenServices); endpoints.authenticationManager(authenticationManager); // 数据库管理授权码
endpoints.authorizationCodeServices(new JdbcAuthorizationCodeServices(
dataSource));
// 数据库管理授权信息
ApprovalStore approvalStore = new JdbcApprovalStore(dataSource);
endpoints.approvalStore(approvalStore);
}
}
3、配置security
- 配置使用数据库保存登录用户信息
- 配置自定义登录页面
- 暂时禁用CSRF
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired
private UserDetailsService userDetailsService; @Autowired
private PasswordEncoder passwordEncoder; @Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 使用 BCrypt 加密
} public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
authenticationProvider.setHideUserNotFoundExceptions(false);
return authenticationProvider;
} @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/fonts/**",
"/icon/**", "/favicon.ico");
} @Override
protected void configure(HttpSecurity http) throws Exception { http.requestMatchers()
.antMatchers("/login", "/login-error", "/oauth/authorize",
"/oauth/token").and().authorizeRequests()
.antMatchers("/login").permitAll().anyRequest().authenticated(); // 登录页面
http.formLogin().loginPage("/login").failureUrl("/login-error"); // 禁用CSRF
http.csrf().disable();
} @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
} public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("123456"));
}
}
4、application.properties文件配置
server.port=7001 ##### Built-in DataSource #####
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=system
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.dbcp2.initial-size=5
spring.datasource.dbcp2.max-active=25
spring.datasource.dbcp2.max-idle=10
spring.datasource.dbcp2.min-idle=5
spring.datasource.dbcp2.max-wait-millis=10000
spring.datasource.dbcp2.validation-query=SELECT 1
spring.datasource.dbcp2.connection-properties=characterEncoding=utf8 ##### Thymeleaf #####
# 编码
spring.thymeleaf.encoding=UTF-8
# 热部署静态文件
spring.thymeleaf.cache=false
# 使用HTML5标准
spring.thymeleaf.mode=HTML5
九、受保护资源
@RestController
@RequestMapping(value = "/order")
public class TestController { Logger log = LoggerFactory.getLogger(TestController.class); @RequestMapping(value = "/demo")
@ResponseBody
public ResponseMessage getDemo() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
log.info(auth.toString());
return ResponseMessage.success();
}
}
十、应用启动类
@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@MapperScan("org.net5ijy.oauth2.repository")
public class Oauth2Application { public static void main(String[] args) { // args = new String[] { "--debug" }; SpringApplication.run(Oauth2Application.class, args);
}
}
十一、测试授权码模式
1、获取authorization_code授权码
使用浏览器访问:
http://localhost:7001/oauth/authorize?response_type=code&client_id=net5ijy&redirect_uri=http://localhost:8080&scope=all
地址
http://localhost:7001/oauth/authorize
参数
|
response_type |
code |
|
client_id |
根据实际的client-id填写,此处写net5ijy |
|
redirect_uri |
生成code后的回调地址,http://localhost:8080 |
|
scope |
权限范围 |
登录,admin001和123456

允许授权

看到浏览器重定向到了http://localhost:8080并携带了code参数,这个code就是授权服务器生成的授权码

2、获取token令牌
使用curl命令获取token令牌
curl --user net5ijy:123456 -X POST -d "grant_type=authorization_code&scope=all&redirect_uri=http%3a%2f%2flocalhost%3a8080&code=ubtvR4" http://localhost:7001/oauth/token
地址
http://localhost:7001/oauth/token
参数
|
grant_type |
授权码模式,写authorization_code |
|
scope |
权限范围 |
|
redirect_uri |
回调地址,http://localhost:8080需要urlencode |
|
code |
就是上一步生成的授权码 |

返回值
{
"access_token": "c5836918-1924-4b0a-be67-043218c6e7e0",
"token_type": "bearer",
"refresh_token": "7950b7f9-7d60-41da-9a95-bd2c8b29ada1",
"expires_in": 7199,
"scope": "all"
}
这样就获取到了token令牌,该token的访问权限范围是all权限,在2小时后失效。
3、使用token访问资源
http://localhost:7001/order/demo?access_token=c5836918-1924-4b0a-be67-043218c6e7e0

Spring Security实现OAuth2.0授权服务 - 进阶版的更多相关文章
- Spring Security实现OAuth2.0授权服务 - 基础版
一.OAuth2.0协议 1.OAuth2.0概述 OAuth2.0是一个关于授权的开放网络协议. 该协议在第三方应用与服务提供平台之间设置了一个授权层.第三方应用需要服务资源时,并不是直接使用用户帐 ...
- Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端
Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端 目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Se ...
- SimpleSSO:使用Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端
目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模拟OpenID认证 通过authorization co ...
- 使用Spring Security和OAuth2实现RESTful服务安全认证
这篇教程是展示如何设置一个OAuth2服务来保护REST资源. 源代码下载github. (https://github.com/iainporter/oauth2-provider)你能下载这个源码 ...
- nodejs实现OAuth2.0授权服务
OAuth是一种开发授权的网络标准,全拼为open authorization,即开放式授权,最新的协议版本是2.0. 举个栗子: 有一个"云冲印"的网站,可以将用户储存在Goog ...
- spring oauth2 ,spring security整合oauth2.0 JdbcTokenStore实现 解决url-pattern .do .action
参考以下两个文章: http://www.cnblogs.com/0201zcr/p/5328847.html http://wwwcomy.iteye.com/blog/2230265 web.xm ...
- Spring官方宣布:新的Spring OAuth2.0授权服务器已经来了
1. 前言 记不记得之前发过一篇文章Spring 官方发起Spring Authorization Server 项目.该项目是由Spring Security主导的一个社区驱动的.独立的孵化项目.由 ...
- Spring Security 与 OAuth2 介绍
个人 OAuth2 全部文章 Spring Security 与 OAuth2(介绍):https://www.jianshu.com/p/68f22f9a00ee Spring Security 与 ...
- Spring Security 与 OAuth2(介绍)
https://www.jianshu.com/p/68f22f9a00ee Spring Security 与 OAuth2(介绍) 林塬 2018.01.23 11:14* 字数 3097 阅读 ...
随机推荐
- system.exit(int status)中status值不同时的区别
status为0时为正常退出程序,也就是结束当前正在运行中的java虚拟机. status为非0的其他整数(包括负数,一般是1或者-1),表示非正常退出当前程序. 可以明确的是,无论status是什么 ...
- 使用log4Net输出调试信息
在上一篇搭建服务器端的项目基础上,使用log4Net进行调试信息输出 http://www.cnblogs.com/fzxiaoyi/p/8439769.html 1.先分析下Photo 自带的服务器 ...
- Scheme、Claim、ClaimsIdentity、ClaimsPrincipal介绍
在 token 创建.校验的整个生命周期中,都涉及到了 Scheme.Claim.ClaimsIdentity.ClaimsPrincipal 这些概念,如果你之前有使用过微软的 Identity ...
- 协程,yield,i多路复用,复习
课程回顾: 线程池 队列:同一进程内的队列 先进先出 后进先出 优先级队列 线程池中的回调函数是谁在调用? 线程池中的回调函数是子线程调用的,和父线程没有关系 进程池中的会点函数是父进程调用的,和子进 ...
- ABP 依赖报错
一般是自己写的Service 没有实现IApplicationService,和没有继承ApplicationService
- C#中的Queue集合
Queue<T>集合 特点:先进先出,简单来说,就是新添加的元素是顺序添加在集合尾部,但是,移除的时候是从顶部开始移除元素. 三个方法: Enqueue(T obj);//顺序添加一个值到 ...
- Computer-Hunters——项目系统设计与数据库设计
Computer-Hunters--项目系统设计与数据库设计 前言 本次作业属于2019秋福大软件工程实践Z班 本次作业要求 团队名称: Computer-Hunters 本次作业目标:撰写一份针对团 ...
- CF852A Digits
CF852A Digits 隔壁yijian大佬写出了正解.那我就写一个随机化大法吧? 我们先考虑一种错误的贪心,每个数字分成一位,使其分割后数字和最小.虽然这样是错的,但是我们发现错误的概率很小,所 ...
- Spring配置中<bean>的id和name属性
在BeanFactory的配置中,<bean>是我们最常见的配置项,它有两个最常见的属性,即id和name,最近研究了一下,发现这两个属性还挺好玩的,特整理出来和大家一起分享. 1.id属 ...
- Windows彻底卸载VMWare虚拟机详细步骤
不能卸载vmware ,原因是VMware的服务在运行中,停止服务就可以卸载了. 点击开始输入[services.msc],然后点击搜索到服务. 找到这个软件的图一的所有项,然后右键它属性. 全部设置 ...