SpringSecurity+Oauth2.0实战教程

文章目录

    • 一、概念介绍
    • 1.SpringSecurity介绍
    • 2.Oauth2介绍
    • 二、实战应用
    • 1)SpringSecurity
    • 2) SpringSecurity + Oauth2 + redis

一、概念介绍

1.SpringSecurity介绍

SpringSecurity是基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。 核心功能主要包括:认证 、授权 、攻击防护等。 其核心就是一组过滤器链。

2.Oauth2介绍

(1)概念:是用于REST/APIs的代理授权框架;
(2)特性:基于令牌Token的授权,在无需暴露用户密码的情况下,使应用有获取用户资源的权限;
(3)使用场景:常用于APP、第三方服务的授权,如使用微信授权第三方应用系统;
(4)有四种模式:密码模式(不推荐)、授权码模式(常用)、简化模式、客户端模式。

二、实战应用

1)SpringSecurity

1.去官网查看文档,了解下你所使用的版本特性。网址:https://spring.io/projects/spring-security,从Projects-View all projects点进去,找到Spring Security,然后查看相关文档。
在这里插入图片描述
2.SpringSecurity实战
(1)简单实现用户认证功能
①首先创建一个springboot的web项目,并引入springsecurity的依赖
pom.xml如下:


<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.6.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.examplegroupId>
    <artifactId>springsecurityartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>springsecurityname>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>11java.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.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>

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

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

project>

②创建controller和config等类,controller提供入口,config中配置用户名和密码
在这里插入图片描述
TestController:

package com.example.springsecurity.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {

    @RequestMapping(value="/hello")
    @ResponseBody
    public String hello(){
        return "hello";
    }
}

WebSecurityConfig.java:

package com.example.springsecurity.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //用户名密码设置  这里使用的在内存中定义,可以把这里改成通过数据库查询
        auth.inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("admin123")).roles("admin");

    }



}

③验证:进入http://localhost:8080/hello,会弹出登录页面,输入我们设置的用户名及密码即可
在这里插入图片描述
最终结果如下:
在这里插入图片描述
(2)在此基础上,通过角色来控制用户权限。这里使用了自定义的登陆页面,并将用户名密码的配置通过userDetailsService实现。
代码如下:
在这里插入图片描述
①java类
WebSecurityConfig.java:

package com.example.springsecurity.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.List;

@Configuration
//@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //用户名密码设置  可以把这里改成通过数据库查询
        // 这里使用的在内存中定义
        /*auth.inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("admin123")).roles("admin");*/
        //这里使用自定义类的方式
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //自定义登录页面
        http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                //配置接收的用户名密码参数
                .usernameParameter("username")
                .passwordParameter("password")
                //登陆成功后调转的请求
                .defaultSuccessUrl("/index").permitAll();

        //关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
        http.csrf().disable();

        http.authorizeRequests()
                //permitAll()的意思就是放行,这里除了登录页放行,其他的都需要认证
                .antMatchers("/","/login").permitAll()
                //赋权,指定目录下的页面需要特定角色 这里我把admin权限设置的比较高,也能访问role1的页面
                .antMatchers("/admin/2").hasAnyAuthority("admin")
                .antMatchers("/role1/1").hasAnyAuthority("admin,role1");

    }
}


TestController.java

package com.example.springsecurity.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {

    @RequestMapping(value="/hello")
    @ResponseBody
    public String hello(){
        return "hello";
    }

    @RequestMapping("role1/1")
    public String role1Page(){
        return "role1/1";
    }

    @RequestMapping("admin/2")
    public String adminPage(){
        return "admin/2";
    }

    @RequestMapping(value="/")
    public String test1(){
        return "login";
    }

    @RequestMapping("login")
    public String login(){
        return "login";
    }

    @RequestMapping("index")
    public String index(){
        System.out.println("logging...");
        return "index";
    }
}

UserDetailService.java

package com.example.springsecurity.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userDetailsService")
public class UserDetailService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //这里设定角色
        /*List authList = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User("admin",new BCryptPasswordEncoder().encode("admin123"),authList);*/
        List<GrantedAuthority> authList = AuthorityUtils.commaSeparatedStringToAuthorityList("role1");
        return new User("role1",new BCryptPasswordEncoder().encode("123"),authList);
    }
}

②html类
index.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    登录成功!
    <br/>
    <a href="/role1/1">role1 a>
    <br/>
    <a href="/admin/2">admin a>
body>
html>

login.thml

DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example title>
head>
<body>
<div th:if="${param.error}">
    用户名或密码不正确。
div>

<form th:action="@{/login}" method="post">
    <div><label> 用户名 : <input type="text" name="username"/> label>div>
    <div><label> 密码::<input type="password" name="password"/> label>div>
    <div><input type="submit" value="提交"/>div>
form>
body>
html>


1.html

DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example title>
head>
<body>
        role1 Page
body>
html>

2.html

DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example title>
head>
<body>
        admin Page
body>
html>

③其他(pom文件没有修改)
application.yml

spring:
    thymeleaf:
      #模板的模式,支持 HTML, XML TEXT JAVASCRIPT
      mode: HTML5
      #开发配置为false,避免修改模板还要重启服务器
      cache: false


④测试
第一步:UserDetailService 中使用role1的用户名及密码
在这里插入图片描述
然后登录
在这里插入图片描述
点击role1,可以进入;但是点击admin,显示403,没权限访问
在这里插入图片描述
在这里插入图片描述
第二步:UserDetailService 中使用admin的用户名及密码,修改如下图
在这里插入图片描述
然后重启,按照上面的步骤再操作一次(用户名密码要用admin的),admin的页面可以进去。
在这里插入图片描述

2) SpringSecurity + Oauth2 + redis

1.Oauth2介绍
(1)概念:是用于REST/APIs的代理授权框架,它指定了将长期的令牌替换为短期的令牌的策略;
(2)特性:基于令牌Token的授权,在无需暴露用户密码的情况下,使应用有获取用户资源的权限;
(3)使用场景:常用于APP、第三方服务的授权,如使用微信授权第三方应用系统;
(4)有四种模式:密码模式(不推荐)、授权码模式(常用)、简化模式、客户端模式。

2.SpringCloudSecurity + Oauth2实践
(1)不使用redis的用法
pom.xml:

<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.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        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.3.RELEASEversion>
        dependency>

        
        <dependency>
            <groupId>javax.xml.bindgroupId>
            <artifactId>jaxb-apiartifactId>
            <version>2.3.0version>
        dependency>
        <dependency>
            <groupId>com.sun.xml.bindgroupId>
            <artifactId>jaxb-implartifactId>
            <version>2.3.0version>
        dependency>
        <dependency>
            <groupId>com.sun.xml.bindgroupId>
            <artifactId>jaxb-coreartifactId>
            <version>2.3.0version>
        dependency>
        <dependency>
            <groupId>javax.activationgroupId>
            <artifactId>activationartifactId>
            <version>1.1.1version>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettucegroupId>
                    <artifactId>lettuce-coreartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
        dependency>

授权服务器AuthorizationServerConfig :

package com.example.testsecurity.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    // 该对象用来支持 password 模式
    @Autowired
    AuthenticationManager authenticationManager;

    // 该对象用来将令牌信息存储到内存中
    @Autowired(required = false)
    TokenStore inMemoryTokenStore;

    // 该对象将为刷新token提供支持
    @Autowired
    UserDetailsService userDetailsService;

    // 指定密码的加密方式
    @Bean
    PasswordEncoder passwordEncoder() {
        // 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
        return new BCryptPasswordEncoder();
    }

    // 该对象用来将令牌信息存储到Redis中
    @Autowired
    RedisConnectionFactory redisConnectionFactory;


    // 配置 password 授权模式
    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
                .withClient("client1")
                // 该client允许的授权类型 authorization_code,password,refresh_token
                .authorizedGrantTypes("authorization_code","password", "refresh_token")
                .accessTokenValiditySeconds(1800) // 配置access_token的过期时间
                .resourceIds("hello") //配置资源id
                .scopes("all")
                .secret(passwordEncoder().encode("123"));
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(inMemoryTokenStore) //配置令牌的存储(这里存放在内存中)
        //endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) //配置令牌的存储(这里存放在redis中)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        // 表示支持 client_id 和 client_secret 做登录认证
        security.allowFormAuthenticationForClients();
    }
}


资源服务器ResourceServerConfig :

package com.example.testsecurity.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {

        resources.resourceId("hello") // 配置资源id,这里的资源id和授权服务器中的资源id一致
                .stateless(true); // 设置这些资源仅基于令牌认证
    }

    // 配置 URL 访问权限
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/role1/**").hasRole("role1")
                .anyRequest().authenticated();
    }
}

security配置:

package com.example.testsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {

        return super.userDetailsService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("admin")
                .and()
                .withUser("role1")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("role1");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/oauth/**").authorizeRequests()
                //放行授权的请求
                .antMatchers("/oauth/**").permitAll()
                //关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
                .and().csrf().disable();
    }
}

测试入口HelloController:

package com.example.testsecurity.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin";
    }

    @GetMapping("/role1/hello")
    public String user() {
        return "hello role1";
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

测试:注意这里必须用postman,首先要获取access_token
在这里插入图片描述
然后通过accesstoken可以访问对应的资源,其中除role1的资源外其他资源均可访问
http://localhost:8080/hello?access_token=3645490f-9822-4adb-8dc7-1b59c5b8cf8c
http://localhost:8080/admin/hello?access_token=3645490f-9822-4adb-8dc7-1b59c5b8cf8c
http://localhost:8080/role1/hello?access_token=3645490f-9822-4adb-8dc7-1b59c5b8cf8c

(2)使用redis:
1.首先我们了解下使用redis储存token的好处:
token具有时效性,redis有过期功能处理有优势;
可以在多个服务之间实现令牌共享

redis安装使用参考:https://blog.csdn.net/tttalk/article/details/122042206?spm=1001.2014.3001.5501

2.使用代码
application.yml:

spring:
  redis:
    timeout: 6000ms
    # redis数据库索引(默认为0)
    database: 0
    host: 192.168.124.128
    port: 6379
    lettuce:
      pool:
        max-active: 1000 #连接池最大连接数(使用负值表示没有限制)
        max-idle: 10 #连接池中的最大空闲连接
        min-idle: 3 #连接池中的最小空闲连接
        max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
    password: 123

在AuthorizationServerConfig中将令牌放到redis中:
在这里插入图片描述

重新进行access_token的请求,可以看到已经存在redis中了
在这里插入图片描述

你可能感兴趣的:(新手入门教程,java,spring,boot,spring)