Spring Security入门

认证:系统认为用户是否能登录
授权:系统判断用户是否有权限去做某件事情

https://blog.csdn.net/zhanduo0118/article/details/112093781
SpringSecurity入门

项目代码
https://gitee.com/galen.zhang/spring-security-demo

创建Maven项目spring-security-demo,引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

创建静态文件
login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form method="post" action="/login">
    username:<input type="text" name="username"><br>
    password:<input type="password" name="password"><br>
    <input type="submit" value="Login">
</form>
</body>
</html>

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Main</title>
</head>
<body>
Login success!
</body>
</html>

启动springboot应用,访问 http://localhost:8080/login.html
默认跳转到spring security的登录页,用户名:user,密码在控制台中输出


自定义登录逻辑

实现UserDetailsService接口,根据用户名查询用户信息

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}


@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (!"admin".equals(username)) {
            throw new UsernameNotFoundException("用户名不存在!");
        }

        String password = passwordEncoder.encode("123");
        // 角色需要以ROLE_开头
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_normal,/insert,/update"));
    }
}

自定义登录页面

https://www.shuzhiduo.com/A/nAJvZMyoJr/
Spring Security即将弃用WebSecurityConfigurerAdapter配置类

https://blog.csdn.net/donglinjob/article/details/108854574
Spring Security(四) 认证过程常用配置

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                // 登录成功后跳转页面,必须是Post请求
                .defaultSuccessUrl("/toMain")
                .loginPage("/login.html");

        // 认证
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();

        http.csrf().disable();
        return http.build();        
    }
}   


@Controller
public class LoginController {

    // 跳转到登录成功页面,必须是POST请求
    @RequestMapping("toMain")
    public String toMain() {
        return "redirect:main.html";
    }
}    

自定义失败页面

添加fail.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
操作失败,请重新登录 <a href="/login.html">跳转</a>
</body>
</html>

配置类

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                // 登录成功后跳转页面,必须是Post请求
                .defaultSuccessUrl("/toMain")
                // 登录失败后跳转页面,必须是Post请求
                .failureForwardUrl("/toFail")
        ;

        // 认证
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")                
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();


        http.csrf().disable();

        return http.build();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

页面跳转

@Controller
public class LoginController {

    @RequestMapping("toMain")
    public String toMain() {
        System.out.println("toMain.....");
        return "redirect:main.html";
    }

    @RequestMapping("toFail")
    public String toFail() {
        System.out.println("toFail.....");
        return "redirect:fail.html";
    }

}

自定义登录用户名参数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form method="post" action="/login">
    username:<input type="text" name="uname"><br>
    password:<input type="password" name="pwd"><br>
    <input type="submit" value="Login">
</form>
</body>
</html>

配置类

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                .usernameParameter("uname")
                .passwordParameter("pwd")
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                // 登录成功后跳转页面,必须是Post请求
                .defaultSuccessUrl("/toMain")
                // 登录失败后跳转页面,必须是Post请求
                .failureForwardUrl("/toFail")
        ;

        // 认证
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")                
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();


        http.csrf().disable();

        return http.build();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

自定义登录成功处理器

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println(request.getRemoteAddr());
        User user = (User) authentication.getPrincipal();
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        System.out.println(user.getAuthorities());
        response.sendRedirect(url);
    }
}

配置类

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                .usernameParameter("uname")
                .passwordParameter("pwd")
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                // 登录成功后跳转页面,必须是Post请求
                //.defaultSuccessUrl("/toMain")
                //自定义成功处理器,与defaultSuccessUrl不能同时使用
                .successHandler(new MyAuthenticationSuccessHandler("main.html"))                
                // 登录失败后跳转页面,必须是Post请求
                .failureForwardUrl("/toFail")
        ;

        // 认证
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")                
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();


        http.csrf().disable();

        return http.build();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

自定义登录失败处理器

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private String url;

    public MyAuthenticationFailureHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

配置类

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                .usernameParameter("uname")
                .passwordParameter("pwd")
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                // 登录成功后跳转页面,必须是Post请求
                //.defaultSuccessUrl("/toMain")
                //自定义成功处理器,与defaultSuccessUrl不能同时使用
                .successHandler(new MyAuthenticationSuccessHandler("main.html"))                
                // 登录失败后跳转页面,必须是Post请求
                //.failureForwardUrl("/toFail")
                .failureHandler(new MyAuthenticationFailureHandler("/fail.html"))
        ;

        // 认证
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")                
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();


        http.csrf().disable();

        return http.build();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

antMatcher路径匹配

https://blog.csdn.net/zhanduo0118/article/details/112093802
SpringSecurity学习记录3

? 匹配一个字符
* 匹配0个或多个字符
** 匹配0个或多个目录

        // 认证授权
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
//                .antMatchers("/fail.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")
                .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
                // 按后缀指定放行文件
                .antMatchers("/**/*.png").permitAll()                
                .anyRequest().authenticated();


regexMatcher正则表达式匹配

        // 认证授权
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
//                .antMatchers("/fail.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")
                .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
                // 匹配.png后缀的文件
                .regexMatchers(".+[.]png").permitAll()
                .regexMatchers(HttpMethod.POST, "/demo").permitAll()
            .anyRequest().authenticated();

mvcMatchers匹配servletPath

application.properties里面加上 
spring.mvc.servlet.path=/spring-intmall

        // 认证授权
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
//                .antMatchers("/fail.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")
                .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
                // 如果指定了servletContext路径(项目名称),需要加上servletPath
                .mvcMatchers("/demo").servletPath("/spring-intmall").permitAll()
                .anyRequest().authenticated();

内置控制访问方法

ExpressionUrlAuthorizationConfigurer包含6种访问方式:
permitAll、denyAll、anonymous、authenticated、fullyAuthenticated、rememberMe

权限判断:hasAuthority("admin") / hasAnyAuthority("admin", "adminN")
        // 认证授权
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").permitAll()
                .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
                // 权限区分大小写
//                .antMatchers("/main1.html").hasAuthority("admin")
                .antMatchers("/main1.html").hasAnyAuthority("admin", "adminN")
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();
角色判断
  1. 在返回的用户中添加角色
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (!"admin".equals(username)) {
            throw new UsernameNotFoundException("用户名不存在!");
        }

        String password = passwordEncoder.encode("123");
        // 角色需要以ROLE_开头
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_normal"));
    }
}
  1. 角色权限判断中不能加前缀ROLE_
        // 认证授权
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").permitAll()
                .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
                // 权限区分大小写
//                .antMatchers("/main1.html").hasAuthority("admin")
//                .antMatchers("/main1.html").hasAnyAuthority("admin", "adminN")
                // 角色校验会自动添加ROLE_,这里不能再加前缀;区分大小写
//                .antMatchers("/main1.html").hasRole("normal")
                .antMatchers("/main1.html").hasAnyRole("normal", "Normal")
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();	
IP地址限制

request.getRemoteAddr():localhost输出的IP地址是 0:0:0:0:0:0:0:1
127.0.0.1和ipconfig获取的ip,在设置hasIpAddress是不一样的

		// 认证授权
       http.authorizeRequests()
               // login.html不需要被认证
               .antMatchers("/login.html").permitAll()
               .antMatchers("/fail.html").permitAll()
               .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
               // 权限区分大小写
//                .antMatchers("/main1.html").hasAuthority("admin")
//                .antMatchers("/main1.html").hasAnyAuthority("admin", "adminN")
               // 角色校验会自动添加ROLE_,这里不能再加前缀;区分大小写
//                .antMatchers("/main1.html").hasRole("normal")
//                .antMatchers("/main1.html").hasAnyRole("normal", "Normal")
               .antMatchers("/main1.html").hasIpAddress("127.0.0.1")
               // 所有请求都必须被认证(登录后访问)
               .anyRequest()
               .authenticated();

自定义403处理方案

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("{\"status\": \"error\", \"msg\": \"权限不足,请联系管理员\"}");
        writer.flush();
        writer.close();
    }
}

配置类

@Configuration
public class SecurityConfig {
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;   

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                .usernameParameter("uname")
                .passwordParameter("pwd")
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                // 登录成功后跳转页面,必须是Post请求
                //.defaultSuccessUrl("/toMain")
                //自定义成功处理器,与defaultSuccessUrl不能同时使用
                .successHandler(new MyAuthenticationSuccessHandler("main.html"))                
                // 登录失败后跳转页面,必须是Post请求
                //.failureForwardUrl("/toFail")
                .failureHandler(new MyAuthenticationFailureHandler("/fail.html"))
        ;

        // 认证
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")                
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();


        http.csrf().disable();

        // 异常处理
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);        

        return http.build();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

access自定义方法实现权限控制

https://blog.csdn.net/donglinjob/article/details/108856052
Spring Security(七) 基于表达式/注解的访问控制

public interface MyService {
    boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

@Service
public class MyServiceImpl implements MyService {
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object obj = authentication.getPrincipal();
        if (obj instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) obj;
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
        }
        return false;
    }
}

配置类

@Configuration
public class SecurityConfig {

    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                .usernameParameter("uname")
                .passwordParameter("pwd")
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                // 登录成功后跳转页面,必须是Post请求
                .defaultSuccessUrl("/toMain")
                //自定义成功处理器,与defaultSuccessUrl不能同时使用
//                .successHandler(new MyAuthenticationSuccessHandler("main.html"))
                // 登录失败后跳转页面,必须是Post请求
                .failureForwardUrl("/toFail")

//                .failureHandler(new MyAuthenticationFailureHandler("/fail.html"))
        ;

        // 认证授权
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
//                .antMatchers("/fail.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")
                .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
                // 如果指定了servletContext路径(项目名称),需要加上servletPath
//                .mvcMatchers("/demo").servletPath("/xxxx").permitAll()
//                .antMatchers("/xxxx/demo").permitAll()
                // 权限区分大小写
//                .antMatchers("/main1.html").hasAuthority("admin")
//                .antMatchers("/main1.html").hasAnyAuthority("admin", "adminN")
                // 角色校验会自动添加ROLE_,这里不能再加前缀;区分大小写
//                .antMatchers("/main1.html").hasRole("normal")
//                .antMatchers("/main1.html").hasAnyRole("normal", "Normal")
//                .antMatchers("/main1.html").hasIpAddress("127.0.0.1")
                // 所有请求都必须被认证(登录后访问)
                //.anyRequest().authenticated()
                .anyRequest().access("@myServiceImpl.hasPermission(request, authentication)")
        ;

        http.csrf().disable();

        // 异常处理
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);

        return http.build();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

基于注解的访问控制

@Secured判断是否具有角色,参数以ROLE_开头。如果权限不足,返回403错误
启用@Secured注解:在springboot启动类上增加注解
@EnableGlobalMethodSecurity(securedEnabled = true)

    //controller方法中增加注解
    @Secured("ROLE_normal")
    @RequestMapping("toMain")
    public String toMain() {
        System.out.println("toMain.....");
        return "redirect:main.html";
    }

@PreAuthorize表示访问方法或类之前判断权限
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

    @PreAuthorize("hasRole('normal')")
    @RequestMapping("toMain")
    public String toMain() {
        System.out.println("toMain.....");
        return "redirect:main.html";
    }

RememberMe功能

自动把用户信息存储到数据库中
引入mybatis依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

配置数据库

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/security
spring.datasource.username=root
spring.datasource.password= root

spring.main.allow-circular-references=true

在页面上加入remeber-me复选框

rememberMe:<input type="checkbox" name="remember-me"><br>

配置类

@Configuration
public class SecurityConfig {
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;   

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 表单提交自定义登录页
        http.formLogin()
                .usernameParameter("uname")
                .passwordParameter("pwd")
                // 登录提交处理请求的地址
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                // 登录成功后跳转页面,必须是Post请求
                //.defaultSuccessUrl("/toMain")
                //自定义成功处理器,与defaultSuccessUrl不能同时使用
                .successHandler(new MyAuthenticationSuccessHandler("main.html"))                
                // 登录失败后跳转页面,必须是Post请求
                //.failureForwardUrl("/toFail")
                .failureHandler(new MyAuthenticationFailureHandler("/fail.html"))
        ;

        // 认证
        http.authorizeRequests()
                // login.html不需要被认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail.html").access("permitAll")                
                // 所有请求都必须被认证(登录后访问)
                .anyRequest()
                .authenticated();


        http.csrf().disable();

        // 异常处理
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);  

        http.rememberMe()
                // token失效时间,秒
                .tokenValiditySeconds(600)
                // form表单字段名称
//                .rememberMeParameter("rememberMe")
                .tokenRepository(getPersistentTokenRepository())
                .userDetailsService(userDetailsService);              

        return http.build();
    }

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动建表,第一次启动时候需要,第二次启动需要注释掉
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

Thymeleaf中Spring Security的使用

https://blog.csdn.net/donglinjob/category_10352718.html
安全管理框架(Spring Security、Shiro)

引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

在template目录中创建文件demo.html

<!DOCTYPE  html>
<html  xmlns= "http://www.w3.org/1999/xhtml"
xmlns: th= = "http://www.thymeleaf.org"
xmlns: sec= = "http://www.thy meleaf.org/thymeleaf-extras-springsecurity5">
	<head>
		<meta  charset= "UTF-8">
		<title>Title</ title>
	</head>
	<body>
		登录账号:<span sec:authentication= "name"></span><br/>
		登录账号:<span sec:authentication= "principal.username"></ span><br/>
		凭证:<span sec:authentication= "credentials"></span><br/>
		权限和角色:<span  sec:authentication= "authorities"></span><br/>
		客户端地址:<span  sec:authentication= "details.remoteAddress"></span><br/>
		sessionId:<span  sec:authentication= "details.sessionId"></span><br/>
		
		
通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button>
<br/>

通过角色判断:
<button sec:authorize="hasRole('normal')">新增</button>
<button sec:authorize="hasRole('normal')">删除</button>
<button sec:authorize="hasRole('normal')">修改</button>

	</ body>
</html

退出登录

实现退出非常简单,只要在页面中添加/logout的超链接即可

<a href="/logout">退出</a>

指定退出后的跳转页面

        http.logout()
                // 退出登录跳转页面
                .logoutSuccessUrl("/login.html");

Oauth2认证

https://blog.csdn.net/CM134cmcm6513/article/details/120543191
认证解决方案

第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。
OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务

Oauth2认证中包括的角色:
客户端 :本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源
资源拥有者 :通常为用户,也可以是应用程序,即该资源的拥有者
授权服务器(也称认证服务器):用来对资源拥有的身份进行认证、对访问资源进行授权。 客户端要想访问资源需要通过认证服务器由资源拥有者授 权后方可访问。
资源服务器 :存储资源的服务器,比如,畅购用户管理服务器存储了畅购的用户信息,微信的资源服务存储了微信的用户信息等。客户端最终访问资源服务器获取资源信息。

 

Authorize Endpoint :授权端点,进行授权
Token Endpoint :令牌端点,经过授权拿到对应的Token
lntrospection Endpoint :校验端点,校验Token的合法性
Revocation Endpoint :撤销端点,撤销授权

https://blog.51cto.com/u_15359644/3803760
Spring Security + OAuth2.0

https://www.modb.pro/db/223918
Spring Cloud学习笔记——Spring Security与OAuth2


Spring Security Oauth2 demo

项目代码
https://gitee.com/galen.zhang/spring-security-oauth2-demo

创建maven项目spring-security-oauth2-demo
引入依赖

       <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
        </dependency>
        <!-- OAuth2 自动配置-->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        </dependency>
        <!-- OAuth2资源服务器-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

根据用户名从数据库中加载用户信息

@Service
public class UserService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123456");
        return new User("admin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

Spring Security中对Oauth2路径忽略权限校验


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "/logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll();
    }

    /**
     * 注意在@EnableAuthorizationServer -> AuthorizationServerConfigurerAdapter里面会自动创建一个WebSecurityConfigurerAdapter实例
     * 在创建SecurityFilterChain时,会出现错误:Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
     */
//    @Bean
//    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//        http.csrf().disable()
//                .authorizeRequests()
//                .antMatchers("/oauth/**", "/login/**", "/logout/**")
//                .permitAll()
//                .anyRequest()
//                .authenticated()
//                .and()
//                .formLogin()
//                .permitAll();
//        return http.build();
//    }
}

授权服务器配置

/**
 * 授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")
                .secret(passwordEncoder.encode("112233"))
                .accessTokenValiditySeconds(3600)
                .redirectUris("http://www.baidu.com")
                .scopes("all")
                .authorizedGrantTypes("authorization_code")
        ;
    }
}

资源服务器配置

/**
 * 资源服务器
 */
@Configuration
@EnableResourceServer
public class ResourServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/user/**")
        ;
    }
}

demo获取数据

@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 获取当前用户
     * @param authentication
     * @return
     */
    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication.getPrincipal();
    }
}

先获取授权码

http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
admin/123456

获取token
http://localhost:8080/oauth/token
PostMan,post方法,Authorization -> Basic Auth: admin/112233
Body -> x-www-form-urlencoded
grant_type=authorization_code
code=
client_id=admin
redirect_uri=http://www.baidu.com
scope=all

获取数据
http://localhost:8080/user/getCurrentUser
Authorization -> Bearer Token

注意在@EnableAuthorizationServer -> AuthorizationServerConfigurerAdapter里面会自动创建一个WebSecurityConfigurerAdapter实例
而在自定义的SecurityConfig类中如果使用到了SecurityFilterChain,会出现错误:Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.


密码模式

授权服务器配置类

/**
 * 授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
        ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")
                .secret(passwordEncoder.encode("112233"))
                .accessTokenValiditySeconds(3600)
                .redirectUris("http://www.baidu.com")
                .scopes("all")
//                .authorizedGrantTypes("authorization_code")
                .authorizedGrantTypes("password")
        ;
    }
}

配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "/logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll();
    }

    /**
     * 注意在@EnableAuthorizationServer -> AuthorizationServerConfigurerAdapter里面会自动创建一个WebSecurityConfigurerAdapter实例
     * 在创建SecurityFilterChain时,会出现错误:Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
     */
//    @Bean
//    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//        http.csrf().disable()
//                .authorizeRequests()
//                .antMatchers("/oauth/**", "/login/**", "/logout/**")
//                .permitAll()
//                .anyRequest()
//                .authenticated()
//                .and()
//                .formLogin()
//                .permitAll();
//        return http.build();
//    }
}

密码模式获取token
http://localhost:8080/oauth/token
PostMan,post方法,Authorization -> Basic Auth: admin/112233
Body -> x-www-form-urlencoded
grant_type=password
username=admin
password=123456
scope=all

再次查询
http://localhost:8080/user/getCurrentUser


Redis存储Token

引入pom依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- redis依赖commons-pool 这个依赖一定要添加 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

Redis配置类

@Configuration
public class RedisConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }
}

授权服务器配置类


/**
 * 授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    @Autowired
    @Qualifier("redisTokenStore")
    private TokenStore tokenStore;

    /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                .tokenStore(tokenStore)
        ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")
                .secret(passwordEncoder.encode("112233"))
                .accessTokenValiditySeconds(3600)
                .redirectUris("http://www.baidu.com")
                .scopes("all")
//                .authorizedGrantTypes("authorization_code")
                .authorizedGrantTypes("password")
        ;
    }
}

jwt

https://jwt.io/

引入pom依赖

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

测试用例

    @Test
    public void testCreateToken() {
        JwtBuilder jwtBuilder = Jwts.builder()
                // 声明的标识{"jti": "8888"}
                .setId("8888")
                // 主体,用户{"sub": "Rose"}
                .setSubject("Rose")
                // {"ita": "xxxx"}
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, "xxxx");
        // 获取token
        String token = jwtBuilder.compact();
        System.out.println(token);

        System.out.println("====================");
        String[] arr = token.split("\\.");
        System.out.println(Base64Codec.BASE64.decodeToString(arr[0]));
        System.out.println(Base64Codec.BASE64.decodeToString(arr[1]));
        System.out.println(Base64Codec.BASE64.decodeToString(arr[2]));
    }

    @Test
    public void parseToken() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY1ODg0OTQxMX0.mTKa5-7ko88bvz7X1yZD5LBvvlZ-KznteLNfpO_6LN0";
        // 解析token
        Claims claims = Jwts.parser().setSigningKey("xxxx")
                .parseClaimsJws(token)
                .getBody();
        System.out.println("id: " + claims.getId());
        System.out.println("subject: " + claims.getSubject());
        System.out.println("issueAt: " + claims.getIssuedAt());
    }

    /**
     * 失效时间
     */
    @Test
    public void testCreateTokenHasExpiration() {
        long now = System.currentTimeMillis();
        // 过期时间1分钟
        long exp = now + 60 * 1000;
        JwtBuilder jwtBuilder = Jwts.builder()
                // 声明的标识{"jti": "8888"}
                .setId("8888")
                // 主体,用户{"sub": "Rose"}
                .setSubject("Rose")
                // {"ita": "xxxx"}
                .setIssuedAt(new Date())
                // 设置过期时间
                .setExpiration(new Date(exp))
                .signWith(SignatureAlgorithm.HS256, "xxxx");
        // 获取token
        String token = jwtBuilder.compact();
        System.out.println(token);

        System.out.println("====================");
        String[] arr = token.split("\\.");
        System.out.println(Base64Codec.BASE64.decodeToString(arr[0]));
        System.out.println(Base64Codec.BASE64.decodeToString(arr[1]));
        System.out.println(Base64Codec.BASE64.decodeToString(arr[2]));
    }

    /**
     * token如果失效,在parseClaimsJws方法中会抛出异常:
     * io.jsonwebtoken.ExpiredJwtException: JWT expired at 2022-07-27T19:59:08Z.
     */
    @Test
    public void parseTokenHasExpiration() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY1ODkyMzA4OCwiZXhwIjoxNjU4OTIzMTQ4fQ.iI_lyvD4Ji3qN2t4-rQdz_fjs8ACuuJFWGWvy1poZYc";
        // 解析token
        Claims claims = Jwts.parser().setSigningKey("xxxx")
                .parseClaimsJws(token)
                .getBody();
        System.out.println("id: " + claims.getId());
        System.out.println("subject: " + claims.getSubject());
        System.out.println("issueAt: " + claims.getIssuedAt());

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("签发时间:" + simpleDateFormat.format(claims.getIssuedAt()));
        System.out.println("过期时间:" + simpleDateFormat.format(claims.getExpiration()));
        System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
    }

    /**
     * 自定义属性
     */
    @Test
    public void testCreateTokenByClaims() {
        JwtBuilder jwtBuilder = Jwts.builder()
                // 声明的标识{"jti": "8888"}
                .setId("8888")
                // 主体,用户{"sub": "Rose"}
                .setSubject("Rose")
                // {"ita": "xxxx"}
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, "xxxx")
                // 自定义属性
                .claim("roles", "admin")
                .claim("logo", "intmall.jpg")
                //直接传入map
                //.addClaims(map)
                ;
        // 获取token
        String token = jwtBuilder.compact();
        System.out.println(token);

        System.out.println("====================");
        String[] arr = token.split("\\.");
        System.out.println(Base64Codec.BASE64.decodeToString(arr[0]));
        System.out.println(Base64Codec.BASE64.decodeToString(arr[1]));
        System.out.println(Base64Codec.BASE64.decodeToString(arr[2]));
    }

    /**
     * 解析自定义属性
     */
    @Test
    public void parseTokenHasByClaims() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY1ODkyMzc4MCwicm9sZXMiOiJhZG1pbiIsImxvZ28iOiJpbnRtYWxsLmpwZyJ9.v7ig1_6wg4V4U6zebFWI0lAKsbeMOEewxpr5v4a5IKA";
        // 解析token
        Claims claims = Jwts.parser().setSigningKey("xxxx")
                .parseClaimsJws(token)
                .getBody();
        System.out.println("id: " + claims.getId());
        System.out.println("subject: " + claims.getSubject());
        System.out.println("issueAt: " + claims.getIssuedAt());
        System.out.println("roles:" + claims.get("roles"));
        System.out.println("logo:" + claims.get("logo"));
    }

Spring Security Oauth2整合jwt

创建jwt token配置类

@Configuration
public class JwtTokenStoreConfig {
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        // 配置签名密钥
        accessTokenConverter.setSigningKey("test_key");
        return accessTokenConverter;
    }
}

授权服务器配置

/**
 * 授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                // 配置存储令牌策略
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
        ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")
                .secret(passwordEncoder.encode("112233"))
                .accessTokenValiditySeconds(3600)
                .redirectUris("http://www.baidu.com")
                .scopes("all")
//                .authorizedGrantTypes("authorization_code")
                .authorizedGrantTypes("password")
        ;
    }
}

使用密码模式做测试:
密码模式获取token
http://localhost:8080/oauth/token
PostMan,post方法,Authorization -> Basic Auth: admin/112233
Body -> x-www-form-urlencoded
grant_type=password
username=admin
password=123456
scope=all
获取到jwt格式的token,可以到 https://jwt.io/ 解析查看令牌里面的数据

再次查询
http://localhost:8080/user/getCurrentUser


扩展jwt中存储的内容

/**
 * jwt内容增强器
 */
 @Component
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> info = new HashMap<>();
        info.put("customParam", "testData");
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}

授权服务器配置

/**
 * 授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置jwt内容增强器
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(delegates);
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                // 配置存储令牌策略
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain)
        ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")
                .secret(passwordEncoder.encode("112233"))
                .accessTokenValiditySeconds(3600)
                .redirectUris("http://www.baidu.com")
                .scopes("all")
                .authorizedGrantTypes("password")
        ;
    }
}

使用密码模式做测试,把token拿到 https://jwt.io/ 做解析

在代码中解析jwt自定义内容
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 获取当前用户
     * @param authentication
     * @return
     */
    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication, HttpServletRequest request) {
        String head = request.getHeader("Authorization");
        String token = head.substring("Bearer ".length());
//        return authentication.getPrincipal();
        return Jwts.parser().setSigningKey("test_key".getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();
    }
}

刷新令牌RefreshToken

授权服务器配置中增加refresh_token

/**
 * 授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置jwt内容增强器
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(delegates);
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                // 配置存储令牌策略
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain)
        ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")
                .secret(passwordEncoder.encode("112233"))
                .accessTokenValiditySeconds(3600)
                .redirectUris("http://www.baidu.com")
                .scopes("all")
                .authorizedGrantTypes("password", "refresh_token", "authorization_code")
        ;
    }
}

使用密码模式做测试,获取到token,refresh_token

使用refresh_token获取新的令牌
http://localhost:8080/oauth/token
PostMan,post方法,Authorization -> Basic Auth: admin/112233
Body -> x-www-form-urlencoded
grant_type=refresh_token
refresh_token=


Spring Security Oauth2整合SSO

https://blog.csdn.net/CM134cmcm6513/article/details/120543191
认证解决方案

创建一个maven子项目spring-security-oauth2-client
修改配置文件application.properties

server.port=8081
#防止Cookie冲突,冲突会导致登录验证不通过
server.servlet.session.cookie.name=OAUTH2-CLIENT-DEMO1
#授权服务器地址
oauth2-server-url: http://localhost:8080
#与授权服务器对应的配置
security.oauth2.client.client-id=admin
security.oauth2.client.client-secret=112233
security.oauth2.client.user-authorization-uri=${oauth2-server-url}/oauth/authorize
security.oauth2.client.access-token-uri=${oauth2-server-url}/oauth/token
security.oauth2.resource.jwt.key-uri=${oauth2-server-url}/oauth/token_key

在启动类上添加@EnableOAuth2Sso注解来启用单点登录功能

@SpringBootApplication
@EnableOAuth2Sso
public class Oauth2ClientDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(Oauth2ClientDemoApplication.class, args);
    }
}

添加接口用于获取当前登录用户信息

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication;
    }
}

修改认证服务器配置
修改授权服务器中的AuthorizationServerConfig类,将绑定的跳转路径为
http://localhost:8081/login,并添加获取秘钥时的身份认证

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            //配置client_id
            .withClient("admin")
            //配置client-secret
            .secret(passwordEncoder.encode("112233"))
            //配置访问token的有效期
            .accessTokenValiditySeconds(3600)
            //配置刷新token的有效期
            .refreshTokenValiditySeconds(864000)
            //配置redirect_uri,用于授权成功后跳转
            // .redirectUris("http://www.baidu.com")
            //单点登录时配置
            .redirectUris("http://localhost:8081/login")
            //配置申请的权限范围
            .scopes("all")
            //自动授权配置
            .autoApprove(true) 
            //配置grant_type,表示授权类型
            .authorizedGrantTypes("password", "refresh_token", "authorization_code");
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
    // 获取密钥需要身份认证,使用单点登录时必须配置
    security.tokenKeyAccess("isAuthenticated()");
}  

测试
启动授权服务和客户端服务
访问客户端需要授权的接口 http://localhost:8081/user/getCurrentUser
会跳转到授权服务的登录界面 http://localhost:8080/login
用户名密码admin/123456

授权后会跳转到原来需要权限的接口地址,展示登录用户信息

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GalenZhang888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值