2021-08-28

**单点登录系统教程

**
1.首先创建一个父工程
在这里插入图片描述
*
1.)给父工程添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--
        <parent>
            <artifactId>spring-boot-starter-parent</artifactId>
            <groupId>org.springframework.boot</groupId>
            <version>2.3.2.RELEASE</version>
        </parent>
        -->


    <groupId>com.jt</groupId>
    <artifactId>02.sca</artifactId>
<!--    添加子工程的时候会自动添加-->
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <!--        删了这个版本8就会出现1.5-->
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <modules>
<!--        添加子工程的时候会自动添加-->
        <module>sca-resource</module>
        <module>sca-resoure-ui</module>
        <module>sca-resource-gateway</module>
        <module>sca-auth</module>
    </modules>

    <dependencyManagement>




        <dependencies>
            <!--Spring boot 依赖(定义了微服务规范)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
            <!--Spring Cloud 依赖(定义了微服务规范)-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
            <!--Spring Cloud Alibaba依赖(基于spring微服务规范做了具体落地实现)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>

        </dependencies>
    </dependencyManagement>


    <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <!--             定义排除的依赖-->
        <exclusions>
            <exclusion>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
            </exclusion>
        </exclusions>

    </dependency>
    </dependencies>
</project>

2.创建子项目*
在这里插入图片描述
1.)子项目pom文件

<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>02.sca</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sca-auth</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
<dependencies>
    <dependency>
<!--        spring启动类-->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
<!--        授权认证-->
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>

<!--        nacos被发现-->
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
<!--        nacos配置文件-->
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>



</dependencies>
</project>

1.5)在子工程下面创建yml配置文件

在sca-auth工程中创建bootstrap.yml文件
server:
   port: 8071
spring:
  application:
    name: sca-auth
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848

1.6) 创建启动类

package com.jt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

2.2)现在启动并访问项目:系统会默认生成一个登陆密码 然后打开浏览器输入http://localhost:8071呈现登陆页面, 登录用户名就是user 默认密码为:密码为启动类自己打印结果生成的默认密码
在这里插入图片描述
在这里插入图片描述

理解:
构造令牌配置:JWT(Json Web Token)是一种json格式 ,将信息转化为json格式,然后加密保存到客户端前端ui,然后带着jwt访问其他模块

令牌有三个部分构成:
HEADER :算法和格式
PAYLOAD:存储用户信息
VERIFY SIGNATURE:把上面两个拿来加密,然后生成令牌

自定义登陆逻辑
令牌流程:前端ui 访问 网关 然后访问 认证获取令牌 返回 token(jwt)令牌到网关 再返回到 客户端ui 前端客户端ui就可以带着token(jwt)令牌就可以不用登录就可以访问网关上的其他资源

实现登录时,会在UI工程中,定义登录页面(login.html),然后在页面中输入自己的登陆账号,
登陆密码,将请求提交给网关,然后网关将请求转发到auth工程,登陆成功和失败要返回json数据,在这个章节我们会按这个业务逐步进行实现

2.)在子项目auth里面创建一个配置包和配置类

定义安全配置类 添加登录成功或失败的处理逻辑
配置类:
package com.jt.config;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /**在这个方法中定义登录规则
     * 1.对所有请求方向
     * 2.登录成功信息的返回
     * 登录失败信息的返回
     * */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        CSRF 攻击是黑客借助受害者的 cookie 骗取服务器的信任,但是黑客并不能拿到 cookie,也看不到 cookie 的内容。
//        为什么有些框架(比如Spring Security)里防护CSRF的filter限定的Method是POST/PUT/DELETE等,而没有限定GET Method?
//        如果你只是创建一个非浏览器客户端使用的服务,你可能会想要禁用CSRF保护
        //关闭跨域工具
        HttpSecurity disable = http.csrf().disable();  // 默认是启用的,需要禁用CSRF保护

        //放行所有请求
        http.authorizeRequests().anyRequest().permitAll();
        //登录成功与失败的处理
        http.formLogin()
                .successHandler(successHandler())
                .failureHandler(failureHandler());

    }

    private AuthenticationSuccessHandler successHandler(){
//        return new AuthenticationSuccessHandler() {
//            @Override
//            public void onAuthenticationSuccess(HttpServletRequest request,
//                                                HttpServletResponse response,
//                                                Authentication authentication) {
//
//            }
//        };
        //1.构建map对象,封装响应数据
        return ( request, response, authentication) ->{
            HashMap<String, Object> map = new HashMap<>();
            map.put("state", 200);
            map.put("message", "login ok");
            writeJsonToClient(response,map);
        };
        }
        @Bean
        public AuthenticationFailureHandler failureHandler(){
        return ( httpServletRequest,
                httpServletResponse,
                e) -> {

            };
        }
        private void writeJsonToClient(HttpServletResponse response,Object map) throws IOException {
            //2.将对象转换为json
            //Gson-->tojson(需要自己找依赖)
            //fastjon->JSON(spring-cloud-starter-alibaba-sentinel)
            //jackson-->writeValueAsString(spring-boot-starter-wed)
            String jsonStr = new ObjectMapper().writeValueAsString(map);
            //3.将json字符串写到客户端
            PrintWriter writer = response.getWriter();
            writer.println(jsonStr);
            writer.flush();
        }
        }


定义用户信息处理对象
在spring security应用中底层会借助UserDetailService对象获取数据库信息,并进行封装,最后返回给认证管理器,完成认证操作,例如:
在这里插入图片描述

package com.jt.auth.service;   注意包名

import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * 登录时用户信息的获取和封装会在此对象进行实现,
 * 在页面上点击登录按钮时,会调用这个对象的loadUserByUsername方法,
 * 页面上输入的用户名会传给这个方法的参数
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    //UserDetails用户封装用户信息(认证和权限信息)

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        //1.基于用户名查询用户信息(用户名,用户状态,密码,....)
        //Userinfo userinfo=userMapper.selectUserByUsername(username);
        String encodedPassword=passwordEncoder.encode("123456");
        //2.查询用户权限信息(后面会访问数据库)
        //这里先给几个假数据
        List<GrantedAuthority> authorities =
        AuthorityUtils.createAuthorityList(//这里的权限信息先这么写,后面讲
                "sys:res:create", "sys:res:retrieve");
        //3.对用户信息进行封装
        return new User(username,encodedPassword,authorities);
    }
}

创建网关项目

网关中登陆路由配置
在网关配置文件中添加登录路由配置,例如

  • id: router02
    uri: lb://sca-auth #lb表示负载均衡,底层默认使用ribbon实现
    predicates: #定义请求规则(请求需要按照此规则设计)
    • Path=/auth/login/** #请求路径设计
      filters:
    • StripPrefix=1 #转发之前去掉path中第一层路径

yml配置文件必须是bootstrap.yml文件
在这里插入图片描述

基于Postman进行访问测试
启动sca-gateway,sca-auth服务,然后基于postman访问网关,执行登录测试,例如:
http://localhost:9000/auth/login?username-jack&password=123456
暂时固定写法:用户名username 自定义
暂时固定写法:密码password 123456

2.1)auth下面的配置包里面的配置制成令牌的配置类
颁发登陆成功令牌 构建令牌配置对象
本次我们借助JWT(Json Web Token-是一种json格式)方式将用户相关信息进行组织和加密,并作为响应令牌(Token),从服务端响应到客户端,客户端接收到这个JWT令牌之后,将其保存在客户端(例如localStorage),然后携带令牌访问资源服务器,资源服务器获取并解析令牌的合法性,基于解析结果判定是否允许用户访问资源

package com.jt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class TokenConfig {
    //定义签名key,在执行令牌签名需要这个key,可以自己指定.
    private String SIGNING_KEY = "auth";

    //定义令牌生成策略.
//    构建令牌生成对象    TokenStore是一个接口
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());  //创建一个jwt格式
    }
    //定义Jwt转换器,负责生成jwt令牌,解析令牌内容
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
        //设置加密/解密口令
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }

}

自定义登陆页面 前端客户端
在sca-resource-ui工程的static目录中定义登陆页面,例如:
在这里插入图片描述

<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>login</title>
</head>
<body>
<div class="container"id="app">
    <h3>Please Login</h3>
    <form>
        <div class="mb-3">
            <label for="usernameId" class="form-label">Username</label>
            <input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
        </div>
        <div class="mb-3">
            <label for="passwordId" class="form-label">Password</label>
            <input type="password" v-model="password" class="form-control" id="passwordId">
        </div>
        <button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
    </form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    var vm=new Vue({
        el:"#app",//定义监控点,vue底层会基于此监控点在内存中构建dom树
        data:{ //此对象中定义页面上要操作的数据
            username:"",
            password:""
        },
        methods: {//此位置定义所有业务事件处理函数
            doLogin() {
                //1.定义url
                let url = "http://localhost:9000/auth/login"  路径是连到网关的,看你的网关链接路径是哪个
                //2.定义参数

                let params = new URLSearchParams()
                params.append('username',this.username);
                params.append('password',this.password);
                //3.发送异步请求
                axios.post(url, params).then((response) => {
                    debugger
                    let result=response.data;
                    console.log(result);
                    if (result.state == 200) {
                        alert("login ok");
                    } else {
                        alert(result.message);
                    }
                })
            }
        }
    });
</script>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个可能的Java实现: ```java import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; public class RentPlanGenerator { private static final double RENT_INCREASE_RATE = 0.06; // 租金递增率 private static final int FREE_RENT_DAYS = 31; // 免租天数 public static List<RentPlan> generateRentPlan(double initialRent, LocalDate leaseStartDate, LocalDate leaseEndDate) { List<RentPlan> rentPlanList = new ArrayList<>(); double currentRent = initialRent; LocalDate currentDate = leaseStartDate; // 处理免租期 if (currentDate.isBefore(leaseStartDate.plusDays(FREE_RENT_DAYS))) { currentDate = leaseStartDate.plusDays(FREE_RENT_DAYS); } while (currentDate.isBefore(leaseEndDate)) { LocalDate nextIncreaseDate = currentDate.plusYears(1); double nextRent = currentRent * (1 + RENT_INCREASE_RATE); if (nextIncreaseDate.isBefore(leaseStartDate.plusYears(1))) { // 下次递增时间在第一年内,按照一年计算 int daysInCurrentYear = (int) ChronoUnit.DAYS.between(currentDate, nextIncreaseDate); rentPlanList.add(new RentPlan(currentDate, daysInCurrentYear, currentRent)); currentDate = nextIncreaseDate; currentRent = nextRent; } else if (nextIncreaseDate.isBefore(leaseEndDate)) { // 下次递增时间在第一年外,按照下次递增时间与租赁结束时间的间隔计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } else { // 下次递增时间在租赁结束时间之后,按照租赁结束时间计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } } return rentPlanList; } public static void main(String[] args) { LocalDate leaseStartDate = LocalDate.of(2021, 3, 1); LocalDate leaseEndDate = LocalDate.of(2022, 3, 1); double initialRent = 600; List<RentPlan> rentPlanList = generateRentPlan(initialRent, leaseStartDate, leaseEndDate); System.out.printf("%-12s%-12s%-12s%n", "时间", "天数", "租金"); for (RentPlan rentPlan : rentPlanList) { System.out.printf("%-12s%-12d%-12.2f%n", rentPlan.getStartDate(), rentPlan.getDays(), rentPlan.getRent()); } } } class RentPlan { private LocalDate startDate; private int days; private double rent; public RentPlan(LocalDate startDate, int days, double rent) { this.startDate = startDate; this.days = days; this.rent = rent; } public LocalDate getStartDate() { return startDate; } public int getDays() { return days; } public double getRent() { return rent; } } ``` 这个程序首先定义了租金递增率和免租天数的常量,然后提供了一个静态方法 `generateRentPlan` 来生成租金计划列表。该方法接受三个参数:初始月租金、租赁开始时间和租赁结束时间。 具体实现时,我们使用循环来逐月生成租金计划。在每次循环中,我们首先计算下次递增租金的时间和金额。然后根据下次递增时间与租赁开始时间的间隔,决定本次循环处理的天数和租金金额。最后将这些信息保存到一个 `RentPlan` 对象中,并添加到租金计划列表中。 在主函数中,我们使用 `generateRentPlan` 方法生成租金计划列表,并以表格形式输出。输出结果如下: ``` 时间 天数 租金 2021-04-01 30 600.00 2021-05-01 31 636.00 2021-06-01 30 674.16 2021-07-01 31 713.57 2021-08-01 31 754.29 2021-09-01 30 796.39 2021-10-01 31 840.94 2021-11-01 30 887.02 2021-12-01 31 934.72 2022-01-01 31 984.12 2022-02-01 28 1035.30 ``` 可以看到,程序正确地根据递增周期和递增率生成了每个月的租金计划,并且考虑了免租期的影响。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值