SpringSecurity+Oauth2+JWT

SpringSecurity+Oauth2+JWT

  • SpringSecurity+Oauth2+JWT
      • 快速入门
      • 自定义登入
      • 自定义登入成功失败处理器
      • 权限判断
      • 自定义异常返回
      • 自定义方法实现权限控制
      • 注解实现权限控制
      • 记住我实现
      • 退出登入
      • CSRF
      • Oauth2认证
      • Oauth2入门授权码模式
      • Oauth2密码模式
      • Jwt入门
      • Oauth2整合Jwt
      • Jwt扩展存储内容
      • Jwt解析
      • SpringSecurityOauth2整合SSO
      • Security+Jwt

SpringSecurity+Oauth2+JWT

快速入门

1. 创建基础项目

file ==> new ==> project ==> Spring Initializr ==>next ==> web(Spring Web)、Security(Spring Security) ==>一直下一步

2. 编写代码进行测试

创建controller

@Controller
public class LoginController {
    @RequestMapping("login")
    public String login(){
        System.out.println("登入成功!");
        return "redirect:main.html";
    }
}

static下创建login.html、main.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
    <input type="submit" value="登入" />
form>
body>
html>
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
登入成功!!!
body>
html>

3. 启动项目进行测试

访问http://localhost:8080/login.html

会进入SpringSecurity框架自带的登入页面 用户默认user 密码控制台生成 输入账号密码跳转到自己定义的login.html页面证明项目搭建完成

自定义登入

1. 定义SecurityConfig

@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }
}

2. 定义UserDetailService实现类

@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private PasswordEncoder pw;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名从数据库中取出用户 这里暂时写死
        if (!"admin".equals(username)){
            throw new UsernameNotFoundException("用户:"+username+"不存在");
        }
        //获取用户的密码 也暂时写死
        String root = pw.encode("root");
        return new User(username,root, 	      AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

3. 编写controller

	@RequestMapping("toMain")
    public String toMain(){
        return "redirect:main.html";
    }
    @RequestMapping("toError")
    public String toError(){
        return "redirect:error.html";
    }

4. 定义SecurityConfig类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //表单提交
        http.formLogin()
                //当访问/login时认为是登入,执行loadUserByUsername方法
                .loginProcessingUrl("/login")
                //自定义登入页面
                .loginPage("/login.html")
                //登入成功的页面
                .successForwardUrl("/toMain")
                //登入失败
                .failureForwardUrl("/toError")
                ;


        /**
         * anyRequest          |   匹配所有请求路径
         * access              |   SpringEl表达式结果为true时可以访问
         * anonymous           |   匿名可以访问
         * denyAll             |   用户不能访问
         * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
         * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
         * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
         * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
         * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
         * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
         * permitAll           |   用户可以任意访问
         * rememberMe          |   允许通过remember-me登录的用户访问
         * authenticated       |   用户登录后可访问
         */
        //授权认证
        http.authorizeRequests()
                .antMatchers("/login.html","/error.html").permitAll()
                //除上述请求任何请求都需要认证
                .anyRequest().authenticated();
        //关闭csrf防护
        http.csrf().disable();
    }

    @Bean
    //强散列哈希加密实现
    public PasswordEncoder getPw() {
        return new BCryptPasswordEncoder();
    }
}

自定义登入成功失败处理器

1. 定义AuthenticationSuccessHandler接口实现类、AuthenticationFailureHandler接口实现类

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.sendRedirect(url);
    }
}
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private String url;

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


    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.sendRedirect(url);
    }
}

2. 修改SecurityConfig配置类

        //表单提交
        http.formLogin()
                //当访问/login时认为是登入,执行loadUserByUsername方法
                .loginProcessingUrl("/login")
                //自定义登入页面
                .loginPage("/login.html")
//                //登入成功的页面
//                .successForwardUrl("/toMain")
                //自定义登入成功处理器 不能与successForwardUrl同时使用否侧报错
                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//                //登入失败
//                .failureForwardUrl("/toError")
               .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

权限判断

1. 根据权限进行过滤

//如果有参数,参数表示权限,则其中任何一个权限可以访问
.antMatchers("/main1.html").hasAnyAuthority("demo")
//如果有参数,参数表示权限,则其权限可以访问
.antMatchers("/main1.html").hasAuthority("demo")

2. 根据角色进行过滤

//如果有参数,参数表示角色,则其中任何一个角色可以访问
.antMatchers("/main1.html").hasAnyRole("test")
//如果有参数,参数表示角色,则其角色可以访问
.antMatchers("/main1.html").hasRole("test")

注意:配置角色时要以ROLE_为前缀

3. 根据ip进行过滤

.antMatchers("/main1.html").hasIpAddress("127.0.0.1")

自定义异常返回

1. 创建AccessDeniedHandler接口实现类

@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();
    }
}

2. 配置SecurityConfig配置类

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

自定义方法实现权限控制

1. 编写接口

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

2. 实现接口

@Service
public class MyServiceImpl implements MyService {
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        //获取当前用户
        Object o = authentication.getPrincipal();
        if (o instanceof UserDetails){
            UserDetails userDetails = (UserDetails) o;
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            //判断拥有权限是否包含
            return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
        }
        return false;
    }
}

3. 修改SecurityConfig配置类

.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");

注解实现权限控制

1. 启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true)

2. controller接口上加注解

//拥有ROLE_test角色权限的用户可以访问
    //@Secured("ROLE_test")
	@PreAuthorize("hasRole('ROLE_test')")
    @RequestMapping("toMain1")
    public String toMain1(){
        return "redirect:main1.html";
    }

记住我实现

1. 所需依赖

 <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-bootartifactId>
            <version>2.2.2version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>

2. 配置数据源

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false&rewriteBatchedStatements=true
    username: root
    password: root

3. 配置SecurityConfig配置类

配置所需bean

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setDataSource(dataSource);
        //自动建表 建完表注释掉
        repository.setCreateTableOnStartup(true);
        return repository;
    }
 	@Autowired
    private UserDetailServiceImpl userDetailServiceImpl;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

configure()方法添加

        //记住我
        http.rememberMe()
                //设置过期时间 单位秒 默认两周
                .tokenValiditySeconds(30)
                .userDetailsService(userDetailServiceImpl)
                .tokenRepository(getPersistentTokenRepository())
                    //设置参数名称
//                .rememberMeParameter()
        ;

login.html新增记住我

<form action="/login" method="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    记住我:<input type="checkbox" name="remember-me" value="true" /><br/>
    <input type="submit" value="登入" />
form>

退出登入

1. 修改main.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
登入成功!!!<a href="/toMain1">跳转a> <a href="/logout">退出a>
body>
html>

2. 配置SecurityConfig配置类

		//退出登入
        http.logout().logoutSuccessUrl("/login.html");	

CSRF

CSRF 利用的是网站对用户网页浏览器的信任。跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作

默认开启生成一个类似于token的码进行身份识别

Oauth2认证

1. Oauth2认证流程:

  1. 客户端请求资源拥有者授权
  2. 资源拥有者返回授权码
  3. 客户端拿到授权码请求验证服务申请令牌
  4. 拿到令牌申请资源服务返回受保护资源

2. 术语

  1. 客户凭证:客户端的clientId和密码用于认证客户
  2. 令牌:授权服务器接收到客户端请求办法的令牌
  3. 作用域:客户端请求访问令牌时,由资源拥有者额外指定的细分权限
  4. 授权码:仅用于授权码授权类型,用于获取访问令牌和刷新令牌
  5. 访问令牌:相当于门禁卡,有了这个门禁卡就可以直接访问
  6. 刷新令牌:相当于刷新门禁卡的过期时间
  7. BearerToken:不管谁拿到Token都可以访问资源
  8. Proof of Possession Token:可以校验client是否对Token有明确的使用权

3. 四种模式

授权码模式、简易授权码模式、密码模式、客户端模式

Oauth2入门授权码模式

**1. ** 创建springboot项目 注入相关依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.2.5.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.csfgroupId>
    <artifactId>spring-security-oauth2artifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>spring-security-oauth2name>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
        <spring-cloud.version>Greenwich.SR2spring-cloud.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
        dependency>
    dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-dependenciesartifactId>
            <version>${spring-cloud.version}version>
            <type>pomtype>
            <scope>importscope>
        dependency>
    dependencies>
dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>
project>

2. 创建定义登入

public class User implements UserDetails {
    private String username;
    private String password;
    public List<GrantedAuthority> authorities;

    public User(String username, String password, List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

    @Override
    public List<GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
@Service
public class UserService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123456");
        return new User("admin",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("getUser")
    public Object getUser(Authentication authentication) {
        return authentication.getPrincipal();
    }
}

3. 创建SecurityConfig配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder getPas() {
        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()
        ;

    }
}

4. 创建授权码服务配置类

/**
 * 授权码服务
 */
@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"))
                //配置Token访问有效期 单位秒
                .accessTokenValiditySeconds(3600)
                //配置授权成功跳转路径
                .redirectUris("http://www.baidu.com")
                //配置申请权限
                .scopes("all")
                //配置授权类型 authorization_code 授权码模式
                .authorizedGrantTypes("authorization_code")
        ;

    }
}

5. 创建资源服务配置类

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //所有请求拦截
                .anyRequest().authenticated()
                .and()
                .requestMatchers()
                //放行
                .antMatchers("/user/**");
    }

}

6. 启动项目访问获取授权码

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

7. 拿到授权码访问http://localhost:8902/oauth/token 获取token

BasicAuth admin 112233
Boby grant_type authorization_code
code 授权码
client_id admin
redirect_uri http://www.baidu.com
scope all

Oauth2密码模式

1. SecurityConfig配置类新增bean

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

2. 修改AuthorizationServerConfig配置类

 @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserService userService;
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //配置用户名
                .withClient("admin")
                //配置密码
                .secret(passwordEncoder.encode("112233"))
                //配置Token访问有效期 单位秒
                .accessTokenValiditySeconds(3600)
                //配置授权成功跳转路径
                .redirectUris("http://www.baidu.com")
                //配置申请权限
                .scopes("all")
                //配置授权类型 authorization_code 授权码模式 password密码模式
                .authorizedGrantTypes("password")
        ;

    }

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

    }
}

3. 测试 访问http://localhost:8902/oauth/token

BasicAuth admin 112233
Boby grant_type password
scope all
username admin
password 123456

4. token存入redis

配置redis

spring:
  redis:
    host: 127.0.0.1
    port: 6379

创建redis配置类

@Configuration
public class RedisConfig {
    @Resource
    private RedisConnectionFactory redisConnectionFactory;

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

}

修改AuthorizationServerConfig

    @Resource(name = "getTokenStore")
    private TokenStore tokenStore;
   @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
        .userDetailsService(userService)
        .tokenStore(tokenStore);
    }

Jwt入门

1. 注入相关依赖

 <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.0version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

2. 编写测试类

 void creatJwt() {
        long l = System.currentTimeMillis();
        l = l+(1*60*1000);
        JwtBuilder jwtBuilder = Jwts.builder()
        //声明标识
                .setId("8888")
                //主体
                .setSubject("Rose")
                //创建日期
                .setIssuedAt(new Date())
                //算法,盐
                .signWith(SignatureAlgorithm.HS256, "xxxx")
                //设置过期时间
                .setExpiration(new Date(l))
                //自定义
                .claim("name","常")
                .claim("mobile","999999999")
                ;
        String token = jwtBuilder.compact();
        System.out.println(token);
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        String[] split = token.split("\\.");
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));


    }

3. token解密

void parsingToken(){
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY2NjMyMzYyMywiZXhwIjoxNjY2MzIzNjgzLCJuYW1lIjoi5bi4IiwibW9iaWxlIjoiOTk5OTk5OTk5In0.Hvmb8-qZydOwpM9yTI07EDxNxqQ06I2DAVyiYVQnlzY";
        Claims claims = Jwts.parser().setSigningKey("xxxx").parseClaimsJws(token).getBody();
        String id = claims.getId();
        String subject = claims.getSubject();
        Date issuedAt = claims.getIssuedAt();
        Date expiration = claims.getExpiration();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(id);
        System.out.println(subject);
        System.out.println(format.format(issuedAt));
        System.out.println(format.format(expiration));
        System.out.println(format.format(new Date()));
        System.out.println(claims.get("name"));
        System.out.println(claims.get("mobile"));

    }

Oauth2整合Jwt

1. 编写Jwt配置类

@Configuration
public class JwtTokenStoreConfig {
    @Bean
    public TokenStore jwtTokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //配置密钥
        converter.setSigningKey("test_ket");
        return converter;
    }
}

2. 修改授权码服务配置类

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserService userService;
    @Resource(name = "jwtTokenStore")
    private TokenStore tokenStore;
    @Resource(name = "jwtAccessTokenConverter")
    private JwtAccessTokenConverter jwtAccessTokenConverter;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //配置用户名
                .withClient("admin")
                //配置密码
                .secret(passwordEncoder.encode("112233"))
                //配置Token访问有效期 单位秒
//                .accessTokenValiditySeconds(3600)
                //配置授权成功跳转路径
//                .redirectUris("http://www.baidu.com")
                //配置申请权限
                .scopes("all")
                //配置授权类型 authorization_code 授权码模式 password密码模式
                .authorizedGrantTypes("password")
        ;

    }

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

Jwt扩展存储内容

1. 创建jwt增强器

public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        HashMap map = new HashMap<>();
        map.put("name","常");
        ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);
        return oAuth2AccessToken;
    }
}

2. 修改Jwt配置类

@Configuration
public class JwtTokenStoreConfig {
    @Bean

    public TokenStore jwtTokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //配置密钥
        converter.setSigningKey("test_ket");
        return converter;
    }
    @Bean
    public JwtTokenEnhancer jwtTokenEnhancer(){
       return new JwtTokenEnhancer();
    }
}

3. 修改AuthorizationServerConfig配置类

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserService userService;
    @Resource(name = "jwtTokenStore")
    private TokenStore tokenStore;
    @Resource(name = "jwtAccessTokenConverter")
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //配置用户名
                .withClient("admin")
                //配置密码
                .secret(passwordEncoder.encode("112233"))
                //配置Token访问有效期 单位秒
//                .accessTokenValiditySeconds(3600)
                //配置授权成功跳转路径
//                .redirectUris("http://www.baidu.com")
                //配置申请权限
                .scopes("all")
                //配置授权类型 authorization_code 授权码模式 password密码模式
                .authorizedGrantTypes("password")
        ;

    }

    /**
     * 密码模式所需配置
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //jwt配置增强器
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        ArrayList<TokenEnhancer> list = new ArrayList<>();
        list.add(jwtTokenEnhancer);
        list.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(list);
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain)
        ;
    }
}

Jwt解析

1. 注入依赖

		<dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.0version>
        dependency>
       

2. 测试

   @RequestMapping("getUser")
    public Object getUser(HttpServletRequest request) {
        String header = request.getHeader("Authentication");
        String[] split = header.split("Bearer ");
        String token = split[1];
        return Jwts.parser().setSigningKey("test_ket".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody();
    }

SpringSecurityOauth2整合SSO

1. 创建新springboot项目注入依赖

<properties>
        <java.version>1.8java.version>
        <spring-cloud.version>Greenwich.SR2spring-cloud.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
        dependency>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.0version>
        dependency>
    dependencies>

Security+Jwt

1. 创建springboot工程注入相关依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.7.5version>
        <relativePath/> 
    parent>
    <groupId>com.csfgroupId>
    <artifactId>spring-security-jwtartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>spring-security-jwtname>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
    properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.security.oauthgroupId>
            <artifactId>spring-security-oauth2artifactId>
            <version>2.3.5.RELEASEversion>
        dependency>

        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.1version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.1.0version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.21version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.12version>
            <scope>providedscope>
        dependency>

    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

2. 配置数据源

server:
  port: 9001
logging:
  level:
    root: debug
spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL,
  `role` varchar(255) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_german2_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

3. 创建JwtUser

public class JwtUser implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    public Collection<? extends GrantedAuthority> authorities;

    //传入User构建JwtUser
    public JwtUser(User user) {
        id = user.getId();
        username = user.getUsername();
        password = user.getPassword();
        //权限
        authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

4. JwtUtil

package com.csf.springsecurityjwt.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtils {
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";

    // TOKEN 过期时间
    public static final long EXPIRATION = 1000 * 60 * 30; // 三十分钟

    public static final String APP_SECRET_KEY = "secret";

    private static final String ROLE_CLAIMS = "rol";

    /**
     * 生成token
     *
     * @param username
     * @param role
     * @return
     */
    public static String createToken(String username, String role) {

        Map<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);

        String token = Jwts
                .builder()
                .setSubject(username)
                .setClaims(map)
                .claim("username", username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, APP_SECRET_KEY).compact();
        return token;
    }


    /**
     * 获取当前登录用户用户名
     *
     * @param token
     * @return
     */
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }

    /**
     * 获取当前登录用户角色
     *
     * @param token
     * @return
     */
    public static String getUserRole(String token) {
        Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("rol").toString();
    }

    /**
     * 获解析token中的信息
     *
     * @param token
     * @return
     */
    public static Claims checkJWT(String token) {
        try {
            final Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
            return claims;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 检查token是否过期
     *
     * @param token
     * @return
     */
    public static boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }

}

5. 创建domain、mapper、service、controller

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String username;

    private String password;

    private String role;
    
}
@Data
public class UserDto implements Serializable {
    private String username;

    private String password;
}

public interface UserMapper extends BaseMapper<User> {
}
public interface IUserService extends IService<User> {
    User findByUsername(String username);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public User findByUsername(String username) {
        LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda();
        wrapper.eq(User::getUsername,username);
        return getOne(wrapper);
    }
}
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserService userServiceImpl;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @PostMapping("saveUser")
    //添加账户
    public User saveUser(@RequestBody UserDto dto){
        User user = new User();
        BeanUtils.copyProperties(dto, user);
        user.setPassword(bCryptPasswordEncoder.encode(dto.getPassword()));
        //判断是否是admin账户
        if("admin".equals(dto.getUsername())){
            user.setRole("ADMIN");
        }else {
            user.setRole("USER");
        }
        userServiceImpl.save(user);
        return user;
    }
    @GetMapping("getUserByName")
    public User getUserByName(@RequestParam String username){
        return userServiceImpl.findByUsername(username);
    }

    @GetMapping("/findAll")
    @PreAuthorize("hasAnyAuthority('ADMIN')")  //这一步很重要 拥有ADMIN权限的用户才能访问该请求
    public List<User> findAll(){
        return userServiceImpl.list();
    }
}

6. 创建登入验证、SecurityConfig、拦截器

@Service
public class JwtUserServiceImpl implements UserDetailsService {
    @Autowired
    private IUserService userServiceImpl;

    @Override
    //根据用户传入的信息去数据库查询是否存在该用户
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userServiceImpl.findByUsername(username);
        if (user != null){
            JwtUser jwtUser = new JwtUser(user);
            return jwtUser;
        }else {
            try {
                throw new UsernameNotFoundException("用户不存在");
            } catch (UsernameNotFoundException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
@EnableWebSecurity
// 只有加了@EnableGlobalMethodSecurity(prePostEnabled=true) 那么在上面使用的 @PreAuthorize(“hasAuthority(‘admin’)”)才会生效
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtUserServiceImpl jwtUserServiceImpl;


    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 这边 通过重写configure(),去数据库查询用户是否存在
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(jwtUserServiceImpl).passwordEncoder(bCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 以/user 开头的请求 不需要进行验证
//                .antMatchers("/user/**").permitAll()
                // 其他都放行了
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager())) // 用户登录拦截
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))  // 权限拦截
                // 不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }


}

/**
 * 验证用户名密码正确后,生成一个token,并将token返回给客户端
 * 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法 ,
 * attemptAuthentication:接收并解析用户凭证。
 * successfulAuthentication:用户成功登录后,这个方法会被调用,我们在这个方法里生成token并返回。
 */
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    /**
     * security拦截默认是以POST形式走/login请求,我们这边设置为走/token请求
     * @param authenticationManager
     */
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/login");
    }

    /**
     * 接收并解析用户凭证
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        // 从输入流中获取到登录的信息
        try {
            User loginUser = new ObjectMapper().readValue(request.getInputStream(), User.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
        System.out.println("jwtUser:" + jwtUser.toString());

        String role = "";
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();
        }

        String token = JwtUtils.createToken(jwtUser.getUsername(), role);
        // 返回创建成功的token  但是这里创建的token只是单纯的token
        // 按照jwt的规定,最后请求的时候应该是 `Bearer token`
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        String tokenStr = JwtUtils.TOKEN_PREFIX + token;
        response.setHeader("token", tokenStr);
    }

    // 失败 返回错误就行
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write("authentication failed, reason: " + failed.getMessage());
    }

}
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {

        String tokenHeader = request.getHeader(JwtUtils.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        super.doFilterInternal(request, response, chain);
    }

    // 这里从token中获取用户信息并新建一个token 就是上面说的设置认证信息
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws Exception {

        String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");

        // 检测token是否过期 如果过期会自动抛出错误
        JwtUtils.isExpiration(token);
        String username = JwtUtils.getUsername(token);
        String role = JwtUtils.getUserRole(token);
        if (username != null) {
            return new UsernamePasswordAuthenticationToken(username, null,
                    Collections.singleton(new SimpleGrantedAuthority(role))
            );
        }
        return null;
    }

}

7. 启动项目开始测试

你可能感兴趣的:(后端,java)