手机网站案例,建设网站地图素材,关于进一步加强网站建设,网站设计制作从哪一、OAuth基本概念
1、什么是OAuth2.0
OAuth2.0是一个开放标准#xff0c;允许用户授权第三方应用程序访问他们存储在另外的服务提供者上的信息#xff0c;而不需要将用户和密码提供给第三方应用或分享数据的所有内容。 2、四种认证方式
1#xff09;授权码模式 2#x…一、OAuth基本概念
1、什么是OAuth2.0
OAuth2.0是一个开放标准允许用户授权第三方应用程序访问他们存储在另外的服务提供者上的信息而不需要将用户和密码提供给第三方应用或分享数据的所有内容。 2、四种认证方式
1授权码模式 2简化模式 3密码模式 4客户端模式 普通令牌只是一个随机的字符串没有特殊的意义当客户带上令牌去访问应用的接口时应用本身无法判断这个令牌是否正确就需要到授权服务器上判断令牌。高并发下检查令牌的网络请求就有可能成为一个性能瓶颈。
改良的方式JWT令牌将令牌对应的相关信息全部冗余到令牌本身这样资源服务器就不再需要发送请求给授权服务器去检查令牌自己就可以读取到令牌的授权信息。JWT令牌的本质就是一个加密的字符串。 3、联合登录和单点登录
单点登录 联合登录 4、实例流程
用户-百度-微信 官方 客户端Client浏览器、微信客户端--本身不存储资源需要通过资源拥有者的授权去请求资源服务器的资源资源拥有者ResourceOwner通常是用户也可以应用程序授权服务器AuthorizationServer用于服务提供者对资源拥有的身份进行认证对访问资源进行授权认证成功后会给客户端发放令牌作为客户端访问资源服务器的凭据。资源服务器ResourceServer存储资源的服务器例如微信通过OA协议让百度获取到自己存储的用户信息而百度通过OA协议让用户可以访问自己的受保护资源。 二、SpringSecurity基本概念
1、认证
用户认证就是判断一个用户的身份是否合法的过程用户去访问系统资源时系统要求验证用户的身份信息身份合法方可继续访问不合法则拒绝访问。
认证是为了保护系统的隐私数据与资源用户的身份合法方可访问该系统的资源。 2、授权
授权是用户认证通过后根据用户的权限来控制用户访问资源的过程拥有资源的访问权限则正常访问没有权限则拒绝访问。
认证是为了保证用户身份的合法性授权则是为了更细粒度的对隐私数据进行划分授权是在认证通过后发生的控制不同的用户能够访问不同的资源。 3、会话
用户认证通过后为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制常见的有基于session方式、基于token方式等。 三、简单的权限模型
1、建表
CREATE TABLE role (id int NOT NULL,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci ROW_FORMAT Dynamic;INSERT INTO role VALUES (1, mobile);
INSERT INTO role VALUES (2, salary);
CREATE TABLE source (id int NOT NULL,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,source varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci ROW_FORMAT Dynamic;INSERT INTO source VALUES (1, admin, mobile);
INSERT INTO source VALUES (2, admin, salary);
INSERT INTO source VALUES (3, manage, mobile);
CREATE TABLE user (id int NOT NULL,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,pass varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci ROW_FORMAT Dynamic;INSERT INTO user VALUES (1, admin, admin);
INSERT INTO user VALUES (2, manage, manage); 2、搭建-properties依赖、构建实体类、连接数据库
3、实现
1LoginRequest
Data
public class LoginRequest {JsonProperty(name)private String name;JsonProperty(pass)private String pass;}
2AuthService
public interface AuthService {UserDO userLogin(LoginRequest request);ListString havaPermission(UserDO userDO);}
3AuthServiceImpl
Service
public class AuthServiceImpl implements AuthService {Autowiredprivate UserMapper userMapper;Autowiredprivate SourceMapper sourceMapper;Overridepublic UserDO userLogin(LoginRequest request) {System.out.println(request);UserDO userDO userMapper.selectOne(new LambdaQueryWrapperUserDO().eq(UserDO::getName, request.getName()).eq(UserDO::getPass, request.getPass()));return userDO;}Overridepublic ListString havaPermission(UserDO userDO) {ListSourceDO sourceDOS sourceMapper.selectList(new LambdaQueryWrapperSourceDO().eq(SourceDO::getName, userDO.getName()));ListString collect sourceDOS.stream().map(obj - obj.getSource()).collect(Collectors.toList());return collect;}}
4SalaryController
RestController
RequestMapping(/salary)
public class SalaryController {GetMapping(/query)public String query() {return salary;}
}
5MobileController
RestController
RequestMapping(/mobile)
public class MobileController {GetMapping(/query)public String query() {return mobile;}
}
6LoginController
Slf4j
RestController
RequestMapping(/loginController)
public class LoginController {Resourceprivate AuthServiceImpl authService;PostMapping(/login)public UserDO login(RequestBody LoginRequest request,HttpServletRequest httpServletRequest,HttpServletResponse response) {UserDO user authService.userLogin(request);if (null ! user) {log.info(user login succeed);httpServletRequest.getSession().setAttribute(currentUser, user);System.out.println((httpServletRequest.getSession()));}else {log.info(user login failed);}return user;}PostMapping(/getCurrentUser)public Object getCurrentUser(HttpSession session) {return session.getAttribute(currentUser);}PostMapping(/logout)public void logout(HttpSession session) {session.removeAttribute(currentUser);}PostMapping(/havaPermission)public ListString havaPermission(UserDO userDO){return authService.havaPermission(userDO);}
} 7MyWebAppConfigurer
Component
public class MyWebAppConfigurer implements WebMvcConfigurer {Resourceprivate AuthInterceptor authInterceptor;/*** 配置权限拦截器** param registry*/Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authInterceptor).addPathPatterns(/**);}/*** 简单配置启动页面** param registry*/Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController(/).setViewName(redirect:/index.html);}
}
8拦截器 -- 登录以后才可以访问
Component
public class AuthInterceptor implements HandlerInterceptor {AutowiredAuthService authService;Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {//1、不需要登录就可以访问的路径String requestURI request.getRequestURI();if (requestURI.contains(.) || requestURI.startsWith(/ loginController)) {return true;}//2、未登录用户直接拒绝访问if (null request.getSession().getAttribute(currentUser)) {response.setCharacterEncoding(UTF-8);response.getWriter().write(please login first);return false;} else {UserDO currentUser (UserDO) request.getSession().getAttribute(currentUser);ListString strings authService.havaPermission(currentUser);//3、已登录用户判断是否有资源访问权限if (requestURI.startsWith(/ mobile /) strings.contains(mobile)) {return true;} else if (requestURI.startsWith(/ salary /) strings.contains(salary)) {return true;} else {response.setCharacterEncoding(UTF-8);response.getWriter().write(no auth to visit);return false;}}}
} 9html
!DOCTYPE html
html
headtitleLogin/title
/head
bodyh1Login/h1form idloginForm methodpostlabel fornamename:/labelinput typetext idname namename requiredbrlabel forpasspass:/labelinput typepass idpass namepass requiredbrbutton typesubmitLogin/button/formscript srchttps://code.jquery.com/jquery-3.6.0.min.js/scriptscript srcscript.js/script
/body
/html
$(document).ready(function() {// 监听表单提交事件$(#loginForm).submit(function(event) {// 阻止表单默认提交行为event.preventDefault();// 获取用户名和密码var name $(#name).val();var pass $(#pass).val();// 发送登录请求$.ajax({url: http://localhost:端口号/loginController/login,type: POST,data: JSON.stringify({name: name, pass: pass}),contentType: application/json,success: function(response) {// 登录成功处理console.log(Login successful);// 可以在此处跳转到其他页面},error: function(xhr, status, error) {// 登录失败处理console.log(Login failed);console.log(xhr.responseText);}});});
});
这样一个简单的demo就完成了接下来测试。
http://localhost:端口号/index.html
进入登录界面后输入账号密码这边ajax没有跳转其他界面只是为了获取Set-Cookie也就是session再次访问http://localhost:1223/mobile/query的时候 就可以查看到mobile的信息了。 四、拓展
基于上述的父工程创建子模块。复用MobileController、SalaryController。
1、依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId
/dependency
2、注解
启动类加注解EnableWebSecurity 3、简单的启动
登录用户名是user密码在启动控制台中会显示。 4、注入密码解析器及用户来源
1MyWebConfig
通过注入一个PasswordEncoder对象实现密码加密。包括CryptPassEncoder、Argon2PasswordEncoder、Pbkdf2PasswordEncoder等。
通过注入一个UserDetailService来管理系统的实体数据如果不自己注入在UserDetailsServiceAutoConfiguration中会默认注入一个包含user用户的UserDetailService。在SpringSecurity中也提供了JdbcUserDetailsManager来实现对数据库的用户信息进行管理。
Configuration
public class MyWebConfig implements WebMvcConfigurer {/*** 默认Url根路径跳转到/login此url为spring security提供** param registry*/Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController(/).setViewName(redirect:/login);}/*** 加密** return*/Beanpublic PasswordEncoder getPassWordEncoder() {return new BCryptPasswordEncoder(10);
// return NoOpPasswordEncoder.getInstance();}/*** 自行注入一个UserDetailsService* 如果没有的话在UserDetailsServiceAutoConfiguration中会默认注入一个包含user用户的InMemoryUserDetailsManager* 另外也可以采用修改configure(AuthenticationManagerBuilder auth)方法并注入authenticationManagerBean的方式。** return*/Beanpublic UserDetailsService userDetailsService() {// // 创建数据源
// DataSource dataSource new DruidDataSource();
// // 设置数据库连接信息
// ((DruidDataSource) dataSource).setUrl(jdbc:mysql://localhost:3306/sys);
// ((DruidDataSource) dataSource).setUsername(root);
// ((DruidDataSource) dataSource).setPassword(root);
// ((DruidDataSource) dataSource).setDriverClassName(com.mysql.cj.jdbc.Driver);
// // 将 DataSource 传递给 JdbcUserDetailsManager 根据接口方式进行拓展表结构不同
// return new JdbcUserDetailsManager(dataSource);
// //自定义
// return new MyUserService();//自定义一个Manager没连接数据库InMemoryUserDetailsManager userDetailsManager new InMemoryUserDetailsManager(User.withUsername(admin).password(getPassWordEncoder().encode(admin)).authorities(mobile, salary).build(),User.withUsername(manager).password(manager).authorities(salary).build(),User.withUsername(worker).password(worker).authorities(mobile).build());return userDetailsManager;}
} 2MyUserService
public class MyUserService implements UserDetailsService {Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//假数据 需要在myWebConfig中注入if(admin.equals(username)){return User.withUsername(admin).password(admin).authorities(mobile, salary).build();}return null;}
} 5、注入校验配置规则 MyWebSecurityConfig
EnableWebSecurity
//public class MyWebSecurityConfig extends SecurityConfigurerAdapterDefaultSecurityFilterChain, HttpSecurity {
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
Autowiredprivate UserDetailsService userDetailsService;Overridepublic void configure(HttpSecurity http) throws Exception {//关闭csrg跨域检查http.csrf().disable().authorizeRequests()//配置资源权限.antMatchers(/**.html, /css/**).permitAll().antMatchers(/mobile/**).hasAuthority(mobile).antMatchers(/salary/**).hasAuthority(salary)//loginController下的请求直接通过.antMatchers(/loginController/**).permitAll()//其他请求需要登录.anyRequest().authenticated().and()//记住我随机的秘钥(强大的随机字符串)、过期时间.rememberMe().userDetailsService(userDetailsService).key(your-remember-me-key).tokenValiditySeconds(86400).and().formLogin()//自定义登录页面
// .loginPage(/index.html).loginProcessingUrl(/login).defaultSuccessUrl(/SuccessUrl.html)//可从默认的login页面登录并且登录后跳转到main.html.failureUrl(/SuccessUrl.html);}
} 自定义登录
http.loginPage()方法配置登录页http.loginProcessingUrl()方法定制登录逻辑。登录页的源码DefaultLoginPageGeneratingFilter。
记住我
登录时提交一个remeber-me的参数值可以是 on 、yes 、1 、 true就会记住当前登录用户的token到cookie中。在登出时会清除记住我功能的cookie。
拦截策略
antMachers()方法设置路径匹配可以用两个星号代表多层路径一个星号代表一个或多个字符问号代表一个字符。
配置对应的安全策略
permitAll()所有人都可以访问。
denyAll()所有人都不能访问。
anonymous()只有未登录的人可以访问已经登录的无法访问。
hasAuthority、hasRole这些是配置需要有对应的权限或者角色才能访问。
AuthenticationManagerBuilder配置认证策略WebSecurity配置补充的Web请求策略。
csrf
Cross—Site Request Forgery 跨站点请求伪造。这是一种安全攻击手段简单来说就是黑客可以利用存在客户端的信息来伪造成正常客户进行攻击。例如你访问网站A登录后未退出又打开一个tab页访问网站B这时候网站B就可以利用保存在浏览器中的sessionId伪造成你的身份访问网站A。我们在示例中是使用http.csrf().disable()方法简单的关闭了CSRF检查。而其实Spring Security针对CSRF是有一套专门的检查机制的。他的思想就是在后台的session中加入一个csrf的token值然后向后端发送请求时对于GET、HEAD、TRACE、OPTIONS以外的请求例如POST、PUT、DELETE等会要求带上这个token值进行比对。当我们打开csrf的检查再访问默认的登录页时可以看到在页面的登录form表单中是有一个name为csrf的隐藏字段的这个就是csrf的token。例如我们在freemarker的模板语言中可以使用添加这个参数。而在查看Spring Security后台有一个CsrfFilter专门负责对Csrf参数进行检查。他会调用HttpSessionCsrfTokenRepository生成一个CsrfToken并将值保存到Session中。
注解级别方法支持
在Configuration支持的注册类上打开注解 EnableGlobalMethodSecurity(prePostEnabled true,securedEnabled true,jsr250Enabled true prePostEnabled属性对应PreAuthorize。
securedEnabled 属性支持Secured注解支持角色级别的权限控制。
jsr250Enabled属性对应RolesAllowed注解等价于Secured。
异常处理
现在前后端分离的状态可以使用ControllerAdvice注入一个异常处理类以ExceptionHandler注解声明方法往前端推送异常信息。 6、获取当前用户信息
Slf4j
RestController
RequestMapping(/loginController)
public class LoginController {GetMapping(/getLoginUserByPrincipal)public String getLoginUserByPrincipal(Principal principal) {return principal.getName();}GetMapping(value /getLoginUserByAuthentication)public String currentUserName(Authentication authentication) {return authentication.getName();}GetMapping(value /username)public String currentUserNameSimple(HttpServletRequest request) {Principal principal request.getUserPrincipal();return principal.getName();}GetMapping(/getLoginUser)public String getLoginUser() {User user (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();return user.getUsername();}
} 五、工作原理 1、结构总结
Spring Security是解决安全访问控制的问题就是认证和授权。
Spring Security的重点是对所有进入系统的请求进行拦截校验每个请求是否能够访问所期望的资源对web资源的保护是通过Filter来实现的。
当初始化Spring Security时在org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration中会往spring容器中注入一个SpringSecurityFilterChain的Servlet过滤器类型为org.springframework.security.web.FilterChainProxy它实现了javax.servlet.Filter因此外部的请求都会经过这个类。 而FilterChainProxy是一个代理真正起作用的是FilterChainProxy中SecurityFilterChain所包含的所有Filter同时这些Filter都已经注入到Spring容器中。但是他们并不直接处理用户的认证和授权而是把其交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。 Spring Security的功能实现主要就是一系列过滤器链相互配合完成的。 SecurityContextPersistenceFilter :
整个拦截过程的入口和出口在请求开始时从配置好的SecurityContextRepository中获取SecurityContext再设置给SecurityContextHolder。请求完成后将SecurityContextHolder持有的SecurityContext再保存到SecurityContextRepository同时清除SecurityContextHolder所持有的securityContext。 UsernamePasswordAuthenticationFilter :
用于处理来自表单提交的认证表单必须提供对应的用户名和密码内部还有登录成功或失败后进行处理的AuthenticationSuccessHandler和AuthenticationFailureHandler。 FilterSecurityInterceptor
用于保护web资源使用AccessDecisionManager对当前用户进行授权访问 ExceptionTranslationFilter
能够捕获来自 FilterChain 所有的异常并进行处理。但是它只会处理两类异常AuthenticationException和AccessDeniedException其它的异常它会继续抛出 2、认证流程 1用户提交用户名、密码SecurityFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到封装为Authentication。
2过滤器将Authentication提交到认证管理器AuthenticationManager进行认证。
3认证成功后认证管理器AuthenticationManager返回一个被填充信息的Authentication实例。
4SecurityContextHolder安全上下文容器将第三步填充了信息的Authentication通过SecurityContextHolder.getContext().setAuthentication(…)赋值到其中。可以看出AuthenticationManager接口是发起认证的出发点实现类为ProviderManager而Spring Security支持多种认证方式因此ProviderManager维护着一个List 列表存放多种认证方式最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为DaoAuthenticationProvider它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。 3、授权流程
1、整体流程
1拦截请求
已认证用户访问受保护的web资源将被SecurityFilterChain中(实现类为DefaultSecurityFilterChain)的 FilterSecurityInterceptor 的子类拦截。
2获取资源访问策略
FilterSecurityInterceptor会从 SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需的权限Collection
3决策
FilterSecurityInterceptor会调用 AccessDecisionManager 进行授权决策若决策通过则允许访问资源否则将禁止访问。关于AccessDecisionManager接口最核心的就是其中的decide方法。这个方法就是用来鉴定当前用户是否有访问对应受保护资源的权限。 2、决策流程
在AccessDecisionManager的实现类ConsensusBased中是使用投票的方式来确定是否能够访问受保护的资源。 AccessDecisionManager中包含了一系列的AccessDecisionVoter讲会被用来对Authentication是否有权访问受保护对象进行投票根据投票结果做出最终角色。 为什么要投票呢
权限可以从多个方面进行配置有角色但是没有资源怎么办呢就需要有不同的处理策略。 AccessDecisionVoter定义了三个方法赞成、拒绝、弃权。 1AffirmativeBased 默认只要有一个投票通过就表示通过。
只要有一个投票通过了就表示通过。如果全部弃权也表示通过。如果没有人投赞成票但是有人投反对票则抛出AccessDeniedException.
2ConsensusBased多数赞成就通过
如果赞成票多于反对票则表示通过如果反对票多于赞成票则抛出AccessDeniedException如果赞成票与反对票相同且不等于0并且属性allowIfEqualGrantedDeniedDecisions的值为true则表示通过否则抛出AccessDeniedException。默认是true。如果所有的AccessDecisionVoter都弃权了则将视参数allowIfAllAbstainDecisions的值而定如果该值为true则表示通过否则将抛出异常AccessDeniedException。默认为false。
3UnanimousBased一票否决。
如果受保护对象配置的某一个ConfigAttribute被任意的AccessDecisionVoter反对了则将抛出AccessDeniedException。如果没有反对票但是有赞成票则表示通过。如果全部弃权了则将视参数allowIfAllAbstainDecisions的值而定true则通过false则抛出AccessDeniedException。Spring Security默认是使用的AffirmativeBased投票器我们同样可以通过往Spring容器里注入的方式来选择投票决定器 选择投票器
Bean
public AccessDecisionManager accessDecisionManager() {ListAccessDecisionVoter? extends Object decisionVoters Arrays.asList(new WebExpressionVoter(),new RoleVoter(),new AuthenticatedVoter(),new MinuteBasedVoter());return new UnanimousBased(decisionVoters);
}
Override
protected void configure(HttpSecurity http) throws Exception {http. //.其他参数.anyRequest().authenticated().accessDecisionManager(accessDecisionManager());
} 3、总结 4、自定义认证
1、自定义登录页面及过程
2、数据源改为数据库获取
这两点在上述都有代码及注释展示。
3、配置方法与资源绑定关系
1代码方式
authenticated() //保护URL需要用户登录
permitAll() //指定URL无需保护一般应用与静态资源文件
hasRole(String role) //限制单个角色访问。角色其实相当于一个ROLE_role的资源。
hasAuthority(String authority) //限制单个权限访问
hasAnyRole(String… roles) //允许多个角色访问.
hasAnyAuthority(String… authorities) //允许多个权限访问.
access(String attribute) //该方法使用 SpEL表达式, 所以可以创建复杂的限制.
hasIpAddress(String ipaddressExpression) //限制IP地址或子网
2注解方式 启动类上加入EnableGlobalMethodSecurity(securedEnabledtrue)开启注解过滤权限权限的方法上使用Secured(Resource)匿名登录IS_AUTHENTICATED_ANONYMOUSLYEnableGlobalMethodSecurity(jsr250Enabledtrue) 开启RolesAllowed 注解过滤权限EnableGlobalMethodSecurity(prePostEnabledtrue)使用表达式时间方法级别的安全性打开后可以使用以下几个注解。 PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问。例如PreAuthorize(hasRole(normal) AND hasRole(admin))PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常。此注释支持使用returnObject来表示返回的对象。例如PostAuthorize(returnObject!null returnObject.username authentication.name) PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果PreFilter 允许方法调用,但必须在进入方法之前过滤输入值 5、会话控制
1、获取当前用户信息 用户认证通过之后为了避免每次操作都要进行认证将用户的信息保存在会话中。SpringSecurity提供了会话管理认证通过后将身份信息放入SecurityContextHolder上下文它与当前线程进行绑定获取用户身份。通过SecurityContextHolder.getContext().getAuthentication()获取信息。 2、会话控制
Override
protected void configure(HttpSecurity http) throws Exception {http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}
机制 描述always 如果没有session就创建一个ifRequired 如果需要在登录时创建一个默认never 不会创建但是如果其他应用中创建了session也会使用stateless 绝对不创建session也不会使用 3、会话超时
在properties文件可直接配置
server.servlet.session.timeout3600s
如果会话超时配置跳转地址
http.sessionManagement()
//session过期
.expiredUrl(/login‐view?errorEXPIRED_SESSION)
//传入的sessionId失效
.invalidSessionUrl(/login‐view?errorINVALID_SESSION); 4、安全会话cookie
在properties文件可直接配置
//true - 浏览器脚本无法访问cookie
server.servlet.session.cookie.http‐onlytrue
//true - cookie仅通过HTTPS链接发送
server.servlet.session.cookie.securetrue 5、退出
http
.and()
//提供系统退出支持使用 WebSecurityConfigurerAdapter 会自动被应用
.logout()
//默认退出地址
.logoutUrl(/logout)
//退出后的跳转地址
.logoutSuccessUrl(/login‐view?logout)
//添加一个LogoutHandler用于实现用户退出时的清理工作.默认 SecurityContextLogoutHandler会被添加为最后一个LogoutHandler
.addLogoutHandler(logoutHandler)
//指定是否在退出时让HttpSession失效默认是true
.invalidateHttpSession(true); 六、分布式系统认证方案
1、分析
1统一认证授权
无论不同类型的用户还是不同类型的客户端采用一致的认证、授权、会话判断机制实现统一认证授权服务。
要实现这种统一的认证方式必须可拓展支持各种认证需求例如密码、二维码等。 2多样的认证场景
例如各种支付之间有不同的安全级别需要对应不同的认证场景。 2应用接入认证
提供扩展和开放的能力提供安全的系统对接机制并且可开放部分API给第三方使用内部服务和外部第三方服务采用统一的接入机制。 2、分布式认证方案
基于Session和基于Token
1基于Session
由服务端保存统一的用户信息只是在分布式环境下将session信息同步到各个服务并对请求进行均衡的负载
session复制 在多台应用服务器之间同步session并使session保持一致对外透明。
session黏贴 当用户访问集群中某台服务器后强制指定后续所有请求都落到此机器。
session集中存储 将session存入分布式缓存中所有服务器应用实例都统一从分布式缓存中获取session信息。 基于session认证的方式可以更好的在服务端对会话进行控制并且安全性较高。但是session机制总体是基于cookie的客户端需要保存sessionI的这样在复杂的客户端上不能有效的使用。随着系统的扩展需要提高session的复制、黏贴、存储的容错性。 2基于Token
服务器不再存储认证数据易维护扩展性强客户端可以把token存在任意地方并且可以实现web和app统一认证机制。
但是客户端信息容易泄露token包含了大量信息因此一般数据量较大而且每次请求需要传递也占宽带。并且token的签名验签操作也会带来负担。 3、选择
通常下选择token的方式可以保证整个系统更灵活的拓展性并且减轻服务端的压力。
在这种情况下一般会独立出统一认证服务UAA和网关两个部分来一起完成认证授权服务。
统一认证服务承载接入方认证、登入、授权以及令牌管理完成实际的用户认证、授权功能。网关会作为整个分布式系统的唯一入口为接入方提供API结合。本身也具有辅助例如监控、负载均衡、缓存、协议转换等功能。核心在于所有的接入方和消费端都通过统一的网关接入微服务在网关层处理所有与业务无关的功能。 七、Spring Security OAuth2.0
1、依赖
基于上述工程的父依赖创建子模块security-uaa和security-salary
主要是以下四个
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId
/dependency
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-oauth2/artifactId
/dependency
dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-jwt/artifactId
/dependency
dependencygroupIdjavax.interceptor/groupIdartifactIdjavax.interceptor-api/artifactId
/dependency 2、security-uaa
1配置文件
server.port1226spring.application.nameuaa‐service
server.servlet.context‐path/uaa 2启动类注解 SpringBootApplication
EnableAuthorizationServer
public class SecurityUaaApplication {public static void main(String[] args) {SpringApplication.run(SecurityUaaApplication.class, args);}} 3TokenConfig
Configuration
public class TokenConfig {//签名keyprivate static final String SIGN_KEYuaa;Beanpublic TokenStore tokenStore(){//使用基于内存的普通令牌
// return new InMemoryTokenStore();//基于JWT令牌
// return new JwtTokenStore(accessTokenConvert());}// Bean
// public JwtAccessTokenConverter accessTokenConvert(){
// JwtAccessTokenConverter converter new JwtAccessTokenConverter();
// converter.setSigningKey(SIGN_KEY);
// return converter;
// }} 4AuthorizationConfig
Configuration
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {Autowiredprivate AuthenticationManager authenticationManager;Autowiredprivate ClientDetailsService clientDetailsService;Autowiredprivate UserDetailsService userDetailsService;Autowiredprivate TokenStore tokenStore;Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {//3、配置令牌端点的安全约束security//oauth/token_key公开.tokenKeyAccess(permitAll())//oauth/check_token公开.checkTokenAccess(permitAll())//表单认证申请令牌.allowFormAuthenticationForClients();}Overridepublic void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {//1、客户端详情服务用户信息或者数据库配置clientDetails//内存方式配置用户信息.inMemory()//clientId.withClient(c1)//客户端秘钥.secret(new BCryptPasswordEncoder().encode(secret))//资源列表.resourceIds(salary,mobile)//该client允许的授权类型.authorizedGrantTypes(authorization_code, password, client_credentials, implicit, refresh_token)//允许的授权范围.scopes(all)//跳转到授权界面.autoApprove(false)//回调地址.redirectUris(https://www.baidu.com);}Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//2、配置令牌的访问端点和令牌服务endpoints//定制授权同意页面.pathMapping(/oauth/confirm_access, /custom/confirm_access)//认证管理器.authenticationManager(authenticationManager)//密码模式的用户信息管理.userDetailsService(userDetailsService)//授权码服务.authorizationCodeServices(authorizationCodeServices())//令牌管理服务.tokenServices(tokenService())//允许请求方式.allowedTokenEndpointRequestMethods(HttpMethod.POST);}/*** token配置** return*/public AuthorizationServerTokenServices tokenService() {DefaultTokenServices service new DefaultTokenServices();//客户端详情服务service.setClientDetailsService(clientDetailsService);//允许令牌自动刷新service.setSupportRefreshToken(true);//令牌存储策略-内存service.setTokenStore(tokenStore);//使用jtwservice.setTokenEnhancer(jwtAccessTokenConverter);//令牌默认有效期2小时service.setAccessTokenValiditySeconds(7200);//刷新令牌默认有效期3天service.setRefreshTokenValiditySeconds(259200);return service;}/*** 设置授权码模式的授权码如何存取暂时用内存方式。** return*/Beanpublic AuthorizationCodeServices authorizationCodeServices() {return new InMemoryAuthorizationCodeServices();}} 核心步骤
1ClientDetailsServiceConfigurer用来配置客户端详情服务ClientDetailsService客户端详情信息在这里进行初始化。能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService)负责查找ClientDetails一个ClientDetails代表一个需要接入的第三方应用。
clientId: 用来标识客户的IDsecret: 客户端安全码scope 用来限制客户端的访问范围如果是空(默认)的话那么客户端拥有全部的访问范围。authrizedGrantTypes客户端可以使用的授权类型默认为空。authorities客户端可以使用的权限redirectUris回调地址 2AuthorizationServerEndpointsConfifigurer用来配置令牌token的访问端点和令牌服务(tokenservices)。
令牌服务
实现一个AuthorizationServerTokenServices这个接口需要继承DefaultTokenServices这个类。可以使用它来修改令牌的格式和令牌的存储。默认情况下在创建一个令牌时是使用随机值来进行填充的。这个类中完成了令牌管理的几乎所有的事情唯一需要依赖的是spring容器中的一个TokenStore接口实现类来定制令牌持久化。
InMemoryTokenStore默认方式。适合在单服务器上运行(即并发访问压力不大的情况下并且他在失败时不会进行备份)。大多数的项目都可以使用这个实现类来进行尝试。也可以在并发的时候来进行管理因为不会被保存到磁盘中所以更易于调试。JdbcTokenStore基于JDBC的实现类令牌会被保存到关系型数据库中。使用这个实现类可以在不同的服务器之间共享令牌信息。类似还有RedisTokenStore基于Redis存储令牌信息。JwtTokenStore(JSON Web Token)把令牌信息全部编码整合进令牌本身这样后端服务可以不用存储令牌相关信息。缺点 那就是撤销一个已经授权的令牌非常困难。通常用来处理一个生命周期较短的令牌以及撤销刷新令牌(refresh_token)。并且令牌会比较大因为他要包含较多的用户凭证信息。
访问端点
配置授权类型(Grant Types)
authenticationManager认证管理器。当选择password(资源所有者密码)这个授权类型时就需要指定authenticationManager对象来进行鉴权。userDetailsService用户主体管理服务。如果设置这个属性说明有一个自定义的UserDetailsService接口的实现或者你可以设置到全局域(例如GlobalAuthenticationManagerConfigurer)上去当设置后那么refresh_token刷新令牌方式的授权类型流程中就会多包含一个检查步骤来确保这个账号是否仍然有效。authorizationCodeServices用来设置授权服务器的主要用于authorization_code 授权码类型模式。implicitGrantService用于设置隐式授权模式的状态。tokenGranter如果设置了这个东东(即TokenGranter接口的实现类)那么授权将会全部由自己掌控并且会忽略掉以上几个属性。这个属性一般是用作深度拓展用途。
配置授权断点的URL(Endpoint URLS)
可以通过pathMapping()方法来配置断点URL的链接地址。即将oauth默认的连接地址替代成其他的URL链接地址。例如spring security默认的授权同意页面/auth/confirm_access就可以通过passMapping()方法映射成自己定义的授权同意页面。 3AuthorizationServerSecurityConfigurer用来配置令牌端点的安全约束. 5web安全配置
Configuration
EnableWebSecurity
EnableGlobalMethodSecurity(prePostEnabled true, securedEnabled true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 从父类加载认证管理器** return* throws Exception*/OverrideBeanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}OverrideBeanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager userDetailsManager new InMemoryUserDetailsManager(User.withUsername(admin).password(passwordEncoder().encode(admin)).authorities(mobile, salary).build(), User.withUsername(manager).password(passwordEncoder().encode(manager)).authorities(salary).build(), User.withUsername(worker).password(passwordEncoder().encode(worker)).authorities(worker).build());return userDetailsManager;}/*** 配置用户的安全拦截策略** param http* throws Exception*/Overrideprotected void configure(HttpSecurity http) throws Exception {//链式配置拦截策略http.csrf().disable()//关闭csrf跨域检查.authorizeRequests().anyRequest().authenticated() //其他请求需要登录.and() //并行条件.formLogin(); //可从默认的login页面登录并且登录后跳转到}} 6测试 客户端模式
客户端向授权服务器发送自己的身份信息请求令牌access_token直接返回 密码模式 简化模式 http://localhost:1226/uaa/oauth/authorize?client_idc1response_typetokenscopeallredirect_urihttps://www.baidu.com
配置自己的端口号请求路径client_id、type、scope、uri会自动跳转到请求登录界面选择approve后跳转到baidu 授权码模式 http://localhost:1226/uaa/oauth/authorize?client_idc1response_typecodescopeallredirect_urihttp://www.baidu.com
跳转到baidu拿到code再请求 验证令牌 3、security-salary
1注解
EnableResourceServer
SpringBootApplication
public class SecuritySalaryApplication {public static void main(String[] args) {SpringApplication.run(SecuritySalaryApplication.class, args);}} 2ResourceServerConfig
Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {public static final String RESOURCE_SALARY salary;//使用JWT令牌需要引入与uaa一致的tokenStore存储策略Autowiredprivate TokenStore tokenStore;Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources//资源ID.resourceId(RESOURCE_SALARY)//使用远程服务验证令牌的服务
// .tokenServices(tokenServices())//使用jwt令牌.tokenStore(tokenStore)//无状态模式.stateless(true);}Overridepublic void configure(HttpSecurity http) throws Exception {http//校验请求.authorizeRequests()// 路径匹配规则.antMatchers(/salary/**)// 需要匹配scope.access(#oauth2.hasScope(all)).and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}/*** 配置access_token远程验证策略** return*/public ResourceServerTokenServices tokenServices() {// DefaultTokenServices services new DefaultTokenServices();RemoteTokenServices services new RemoteTokenServices();services.setCheckTokenEndpointUrl(http://localhost:1226/uaa/oauth/check_token);services.setClientId(c1);services.setClientSecret(secret);return services;}
}
public ResourceServerTokenServices tokenServices()如果资源服务和授权服务是在同一个应用程序上那可以使用DefaultTokenServices这样的话就不用考虑关于实现所有必要的接口一致性的问题。而如果资源服务器是分离的那就必须要保证能够有匹配授权服务提供的ResourceServerTokenServices知道如何对令牌进行解码。 3TokenConfig
Configuration
public class TokenConfig {//签名keyprivate static final String SIGN_KEYuaa;Beanpublic TokenStore tokenStore(){//使用基于内存的普通令牌
// return new InMemoryTokenStore();//基于JWT令牌return new JwtTokenStore(accessTokenConvert());}Beanpublic JwtAccessTokenConverter accessTokenConvert(){JwtAccessTokenConverter converter new JwtAccessTokenConverter();converter.setSigningKey(SIGN_KEY);return converter;}} 4WebSecurityConfig
Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers(/salary/**).hasAuthority(salary).anyRequest().permitAll();}} 5SalaryController
RestController
RequestMapping(/salary)
public class SalaryController {GetMapping(/query)public String query() {return salary;}
} 6测试 八、JWT令牌
一个开放的行业标准(RFC 7519)它定义了一种简单的、自包含的协议格式用于在通信双方传递json对象传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA算法的公私钥来签名方式被篡改。 JWT令牌的优点
基于json非常方便解析
可以在令牌中自定义内容易扩展
通过非对称加密算法及数字签名技术JWT防止篡改安全性高
资源服务使用JWT可以不依赖于认证服务自己完成解析 缺点
令牌较长占据的存储空间比较大 组成
Header
头部包括令牌的类型(JWT)以及使用的哈希算法(如HMAC SHA256 RSA)
Payload
负载内容也是一个对象存放有效信息的地方可以存放JWT提供的现有字段例如 iss(签发者)exp(过期时间戳)sub(面向的用户)等也可以自定义字段。不建议存放敏感信息因为可以解码还原出原始内容。最后将这部分JSON内容使用Base64URL编码就得到了JWT令牌的第二个部分。
Signature
签名用于防止JWT内容被篡改。这个部分使用Base64url将前两部分进行编码编码后使用点(.)连接组成字符串最后使用header中声明的签名算法进行签名。 代码在上一个部分展示。