比shiro更适合与spring体系相结合.主要永用户认证
和用户授权
shiro使用请看此处shiro认证授权加密验证的脚手架搭建
相关引用: b站编程不良人,三更草堂,尚硅谷杨博超
spring security认证相关文章视频
spring security授权相关文章视频
自己总结的项目demo-gitee
自己总结的项目demo-github
FilterSecurityInterceptor | 是一个方法级的权限过滤器,基本位于过滤链的最底部 |
ExceptionTranslationFilter | 是个异常过滤器,用来处理在认证授权过程中地出的异常 |
UsernamePasswordAuthenticationFilter | 对/login的POST请求做拦截,校验表单中用户名,密码。 |
一般来说,常见的安全管理技术栈的组合是这样的
SSM + Shiro.
Spring Boot/Spring Cloud + Spring Security.(springboot自动装配)
接着22位是随机生成的.即密文的前29位是生成的.包含了salt信息
.之后的31位是生成的密文认证
UserDetailsService当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。
如果需要自定义逻辑时,只需要实现UserDetailsService接口即可
两个重要的接口
创建类继承JsernamePasswordAuthenticationFilter.重写三个方法
创建类实现UserDetailService,编写查询数据过程,返回User对象,这个User对象是安全框架提供对象
授权
RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。
引入核心pom
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<mybatis.plus>3.5.1mybatis.plus>
<freemarker>2.3.31freemarker>
<jjwt>0.9.1jjwt>
<redisson>3.17.6redisson>
<commons-lang3>3.12.0commons-lang3>
<kaptcha>0.0.9kaptcha>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>${jjwt}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis.plus}version>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
<version>${freemarker}version>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-boot-starterartifactId>
<version>${redisson}version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>${commons-lang3}version>
dependency>
<dependency>
<groupId>com.github.axetgroupId>
<artifactId>kaptchaartifactId>
<version>${kaptcha}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
dependencies>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.6.3version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
用来加密解密token
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 36 * 60 * 1000L;// 36 * 60 *1000 36分钟
//设置秘钥明文
public static final String JWT_KEY = "admin";
// JWT存储的请求头
public static final String TOKEN_AUTHORIZATION = "Authorization";
// JWT 负载中拿到开头
public static final String TOKEN_BEARER = "Bearer";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 加密后的秘钥 secretKey
*
* @return
*/
public static SecretKey generateKey() {
byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes(StandardCharsets.UTF_8));
return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("yuanjie") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(SignatureAlgorithm.HS256, generateKey()) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 生成jwt
*
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jwt
*
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 创建token
*
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
/**
* 解析获取荷载
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
return Jwts.parser()
.setSigningKey(generateKey())
.parseClaimsJws(jwt)
.getBody();
}
/**
* 从token获取用户名
*
* @param jwt
* @return
* @throws Exception
*/
public static String getUserNameFromToken(String jwt) throws Exception {
return parseJWT(jwt).getSubject();
}
/**
* 验证token是否有效
*
* @param
* @throws Exception
*/
public static boolean validateToken(String token, UserDetails userDetails) throws Exception {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/**
* 判断token是否失效
*
* @param
* @throws Exception
*/
public static boolean isTokenExpired(String token) throws Exception {
Date expired = getExpiredDateFromToken(token);
return expired.before(new Date());
}
/**
* 从token中获取时间
*
* @param
* @throws Exception
*/
public static Date getExpiredDateFromToken(String token) throws Exception {
Claims claims = parseJWT(token);
return claims.getExpiration();
}
/**
* 验证token是否可以被刷新
*
* @param
* @throws Exception
*/
public boolean canRefresh(String token) throws Exception {
return !isTokenExpired(token);
}
/**
* 刷新token
*
* @param token
* @return
*/
public String refreshToken(String token) throws Exception {
return createJWT(getUserNameFromToken(token), JWT_TTL);
}
public static void main(String[] args) throws Exception {
String jwt = JwtUtil.createJWT("123");
System.out.println("加密后" + jwt);
Claims claims = JwtUtil.parseJWT(jwt);
String subject = claims.getSubject();
System.out.println("解密后" + subject);
System.out.println("有效时间:" + claims.getExpiration());
}
}
/**
* @author WangJiaHui
* @description: security配置类
* @ClassName SercurityConfig
* @date 2022/9/7 20:30
*/
@Configuration
public class SercurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Resource
private DataSource dataSource;
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Resource
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Resource
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
@Bean
// 注入BCryptPasswordEncoder编码器
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
// 认证管理器
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
// 放行路径,不走过滤器链
web.ignoring().antMatchers(
"css/**",
"js/**",
"/index.html",
"favicon.ico",
"/captcha");
}
// 重写configure(HttpSecurity http) 自定义登录页面
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login","/logout")
.permitAll() // 放行的url
.anyRequest().authenticated() // 除上述放行的url,其余全部鉴权认证
.and()
.rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60 * 36) // 设置token有效期36min
.userDetailsService(userDetailsService)
.and()
// 关闭csrf
.csrf().disable()
// 基于token,不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.headers()
.cacheControl();// 缓存关闭
// 添加jwt 登录授权过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加自定义未授权和未登录结果返回
http.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint) // 认证失败异常处理器
.accessDeniedHandler(restfulAccessDeniedHandler); // 授权失败异常处理器
}
}
/**
* 自定义实现UserDetail认证
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Resource
AdminMapper adminMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<Admin> wrapper =
new QueryWrapper<Admin>()
.eq("username", username);
Admin admin = adminMapper.selectOne(wrapper);
if(!Optional.ofNullable(admin).isPresent()){
throw new UsernameNotFoundException("用户不存在!");
}
// 鉴权凭证
List<GrantedAuthority> role =
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_sale,ROLE_admin"); // 基于角色访问需要前缀ROLE_
return new LoginEntity(admin,null);
}
}
大致思路:
第一部分
第二部分
@RestController
public class LoginController {
@Resource
private IAdminService adminService;
/**
* 登录之后返回token
*/
@PostMapping("/login")
public RespVO login(@Valid @RequestBody AdminLoginVO adminLoginVO,
HttpServletRequest request){
return adminService.login(adminLoginVO,request);
}
/**
* 获取当前登录用户信息
*/
@GetMapping("/admin/info")
public RespVO getAdminInfo(Principal principal){
if(!Optional.ofNullable(principal).isPresent()){
return null;
}
String username = principal.getName();
return adminService.getAdminByUserName(username);
}
/**
* 退出登录
*/
@PostMapping("/logout")
public RespVO logout(){
return adminService.logout();
}
}
认证授权service处理
AuthenticationManager 是认证管理器,在 Spring Security 中有全局AuthenticationManager,也可以有局部AuthenticationManager。全局的AuthenticationManager用来对全局认证进行处理,局部的AuthenticationManager用来对某些特殊资源认证处理。当然无论是全局认证管理器还是局部认证管理器都是由 ProviderManger 进行实现。 每一个ProviderManger中都代理一个AuthenticationProvider的列表,列表中每一个实现代表一种身份认证方式。认证时底层数据源需要调用 UserDetailService 来实现。
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {
@Resource
UserDetailsService userDetailsService;
@Resource
PasswordEncoder passwordEncoder;
@Resource
private AdminMapper adminMapper;
@Resource
private RedissonClient redissonClient;
@Resource
private AuthenticationManager authenticationManager;
@Override
public RespVO login(AdminLoginVO adminLoginVO, HttpServletRequest request) {
if(!adminLoginVO.getCode().equals(request.getSession().getAttribute("captcha"))){
return RespVO.error("验证码输入错误!");
}
// 登录 authenticationManager最终会调用UserDetailsService实现方法
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(adminLoginVO.getUsername(), adminLoginVO.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// UserDetailsService返回对象
LoginEntity loginEntity = (LoginEntity) authenticate.getPrincipal();
/**
* userDetails会在底层调用username和password字段进行匹配认证.可以在
* AuthenticationEntryPoint中执行认证失败处理
*/
// if(!passwordEncoder
// .matches(adminLoginVO.getPassword(), loginEntity.getAdmin().getPassword())){
// return RespVO.error("用户名或密码不正确");
// }
if(!loginEntity.isEnabled()){
return RespVO.error("账号被禁用,请联系管理员!");
}
// 生成token 即加密username
String jwt = JwtUtil.createJWT(adminLoginVO.getUsername());
Map<String, String> map = new HashMap<>();
map.put("tokenHead",JwtUtil.TOKEN_BEARER);
map.put("token",jwt);
// 键token:username 值loginEntity对象
RBucket<Object> token = redissonClient.getBucket("token:"+loginEntity.getUsername());
token.set(loginEntity,36, TimeUnit.MINUTES);
return RespVO.ok("登录成功!").put(map);
}
@Override
public RespVO logout() {
// 获取SecurityContextHolder中的用户id
// UserDetailsService返回对象
LoginEntity loginEntity = (LoginEntity) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = loginEntity.getUsername();
redissonClient.getBucket("token:" + username).delete();
return RespVO.ok("退出成功!");
}
@Override
// TODO 查库吗?
public RespVO getAdminByUserName(String username) {
QueryWrapper<Admin> wrapper = new QueryWrapper<Admin>()
.eq("username", username);
Admin admin = adminMapper.selectOne(wrapper);
if(!Optional.ofNullable(admin).isPresent()){
return RespVO.error("用户信息不存在!");
}
if(!admin.getEnabled()){
return RespVO.error("用户已被禁用,请联系管理员!");
}
return RespVO.ok().put(admin);
}
}
步骤二.回传token
jwt token回传解密验证数据库信息.
/**
* @ClassName JwtAuthenticationTokenFilter
* @Description jwt token回传解密验证数据库信息.
* @Author YuanJie
* @Date 2022/8/25 20:43
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private RedissonClient redissonClient;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取token
String authHeader = request.getHeader(JwtUtil.TOKEN_AUTHORIZATION);
// token不存在
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(JwtUtil.TOKEN_BEARER)) {
filterChain.doFilter(request, response);
return;
}
// TOKEN_HEADER=TOKEN_HEAD TOKEN 截取token
String authToken = authHeader.substring(JwtUtil.TOKEN_BEARER.length());
// 解析token中的username
String subject;
try {
subject = JwtUtil.getUserNameFromToken(authToken);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
// 从redis中获取用户信息 对应登录加密存储redis的键
String redisKey = "token:" + subject;
// redis的redisson整合框架
RBucket<LoginEntity> bucket = redissonClient.getBucket(redisKey);
LoginEntity loginEntity = bucket.get();
if (!Optional.ofNullable(loginEntity).isPresent()) {
throw new RuntimeException("验证已过期,请重新登录!");
}
// 存入SecurityContextHolder,让后续的过滤器链获取信息 三参构造标记已认证
// TODO 权限信息
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginEntity, null, null);
// 放入spring scurity缓存
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
注入Security的configure方法
// 配置过滤器顺序
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
FilterSecurityInterceptor | 是一个方法级的权限过滤器,基本位于过滤链的最底部 |
FilterInvocationSecurityMetadataSource | 获取某个受保护的安全对象object的所需要的权限信息,是一组ConfigAttribute对象的集合.该Filter中的getAttributes方法获取到的是访问每条路径所需要的角色,并将其存储在类型为ConfigAttribute的Collection中作为返回值;接下来就要判断当前用户角色是否与访问路径所需角色相匹配,实现AccessDecisionManager接口的MyAccessDecisionManager类就是完成这件事的。 |
AccessDecisionManagerHandler | 访问决策管理器 |
启动类或spring-security配置类 标记@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
分别对应三个注解启动
// 角色认证 匹配的字符串需要添加前缀“ROLE_“ 不支持Spring 表达式语言
@GetMapping("/hello1")
@Secured({"ROLE_normal","ROLE_admin"})
public String hello1() {
return "hello1";
}
// 基于权限认证 拥有normal或者admin角色的用户都可以方法helloUser()方法 支持Spring 表达式语言
// 自动拼接ROLE_.要求数据库对应角色也具有ROLE_前缀
@GetMapping("/hello2")
@PreAuthorize("hasAnyRole('normal','admin')")
public String hello2() {
return "hello2";
}
//@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限
@GetMapping("/hello3")
@PostAuthorize(" returnObject!=null && returnObject.username == authentication.name")
public User hello3() {
Object pricipal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user;
if("anonymousUser".equals(pricipal)) {
user = null;
}else {
user = (User) pricipal;
}
return user;
}
@Component("exp")
public class SGExpressionRoot {
public boolean hasAuthority(String authority){
//获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 这里的对象已经经过security过滤器链.认证过滤器已在SecurityContextHolder存储用户信息
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
List<String> permissions = loginUser.getPermissions();
//判断用户权限集合中是否存在authority
return permissions.contains(authority);
}
}
@RequestMapping("/hello")
@PreAuthorize("@exp.hasAuthority('system:dept:list')")
public String hello(){
return "hello";
}
自定义处理器可实现以下接口,并注入容器在SecurityConfig中配置.
AuthenticationEntryPoint 认证失败异常处接口
AccessDeniedHandler 授权失败异常处理接口
AuthenticationSuccessHandler 认证成功处接口
AuthenticationFailureHandler 认证失败处接口
LogoutSuccessHandler 登出成功接口
等
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter writer = response.getWriter();
RespVO respVO = RespVO.error(401,"用户名或密码不正确!");
writer.write(new ObjectMapper().writeValueAsString(respVO));
writer.flush();
writer.close();
}
}
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter writer = response.getWriter();
RespVO respVO = RespVO.error(403,"权限不足请联系管理员");
writer.write(new ObjectMapper().writeValueAsString(respVO));
writer.flush();
writer.close();
}
}
http.formLogin().successHandler(successHandler) // 配置成功处理器
.failureHandler(failureHandler); // 配置认证失败处理器
//配置登出成功处理器
http.logout()
.logoutSuccessHandler(logoutSuccessHandler);
// 配置异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint) // 认证失败异常处理器
.accessDeniedHandler(accessDeniedHandler); // 授权失败异常处理器
①先对SpringBooti配置,运行跨域请求
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}
②Security.Config.java
// 允许跨域
http.cors();
跨站请求伪造(英语:Cross-.site request forgery),也被称为one-click attack或者session
riding,通常缩写为CSRF或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了wb中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
从Spring Security4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。跨域无法请求.
我们可以发现CSRF攻击依靠的是cookie中所携带的认证信息。但是在前后端分离的项目中我们的认证信息其实是token,而token并不是存储中cookie中,并且需要前端代码去把token设置到请求头中才可以,所以CSRF攻击也就不用担心了。
声明: 还未研究透彻
OAuth: OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。
OAuth2.0:【一种协议标准,现在都使用OAuth2.0】实际上就是服务器开放了一些查询用户信息的OpenAPI(例如获取用户信息,动态同步,照片,日志,分享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权,授权成功会拿到token,根据token调用OpenAPI获取用户信息
从上图中我们可以看出六个步骤之中,B是关键,即用户怎样才能给于客户端授权。同时会发现0AUth2中包含四种不同的角色:
该错误详解StackoverflowError Spring Security Oauth clientDetailsService
SecurityConfig
// 重写configure(HttpSecurity http) 自定义登录页面
@Override
protected void configure(HttpSecurity http) throws Exception {
// 前后端分离 不可开启csrf防护,不通过session
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login.html","/login/**").permitAll()
.antMatchers("/test/**").permitAll()
// // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 允许跨域
http.cors();
// 开启oauth2请求
http.oauth2Login();
}
github.*
为自定义,无提示.spring:
application:
name: springSecurityTest
security:
oauth2:
client:
registration:
github:
client-id: xxxxx
client-secret: xxxxxxxxxxxxx
#一定要与重定向回调URL一致
redirect-uri: http://localhost:8080/login/oauth2/code/github
server:
port: 8080
还可以可以用spring-security做自己的认证服务器和授权服务器.但是管理比较混乱.博主水平不足
小坑
建议静态公开路径放在public void configure(WebSecurity web)中放行
注意过滤器配置顺序!