Browse Source

first commit

tags/V2.2.0
chenjiandong 1 year ago
parent
commit
8c31e49c0e
10 changed files with 294 additions and 17 deletions
  1. +1
    -1
      others/To-Use.text
  2. +6
    -11
      tuoheng_oidc_server/src/main/java/com/tuoheng/config/SecurityConfig.java
  3. +81
    -0
      tuoheng_oidc_server/src/main/java/com/tuoheng/config/VerifyCodeFilter.java
  4. +11
    -0
      tuoheng_oidc_server/src/main/java/com/tuoheng/constants/CommonConstant.java
  5. +3
    -0
      tuoheng_oidc_server/src/main/java/com/tuoheng/controller/HealthController.java
  6. +17
    -2
      tuoheng_oidc_server/src/main/java/com/tuoheng/controller/Oauth2Controller.java
  7. +30
    -0
      tuoheng_oidc_server/src/main/java/com/tuoheng/controller/VerifyCodeController.java
  8. +114
    -0
      tuoheng_oidc_server/src/main/java/com/tuoheng/until/VerifyCode.java
  9. +2
    -2
      tuoheng_oidc_server/src/main/resources/logback.xml
  10. +29
    -1
      tuoheng_oidc_server/src/main/resources/templates/login.html

+ 1
- 1
others/To-Use.text View File

@@ -5,8 +5,8 @@ client_id=tuoheng-dsp
&response_type=code
&scope=openid+profile
&redirect_uri=http://192.168.11.11:8086/home
&state=4991a0e66547452286dd56e0d9473a0e

&state=http://192.168.11.11:8086/home?url=4991a0e66547452286dd56e0d9473a0e
&code_challenge=GoX2z51GyLtItvCxPY4fI0q4pzvOVhHy00xcFGQ20os&code_challenge_method=S256&response_mode=query

扩展PKCE协议:(&code_challenge=IHicvKyz0IM1do9-3n9QpHf9xVluBshdD1vCD77gV7s&code_challenge_method=S256&response_mode=fragment)

+ 6
- 11
tuoheng_oidc_server/src/main/java/com/tuoheng/config/SecurityConfig.java View File

@@ -1,7 +1,6 @@
package com.tuoheng.config;

import com.tuoheng.handler.AccessDeniedHandler;
import com.tuoheng.handler.AuthenticationEntryPoint;
import com.tuoheng.mapper.UserMapper;
import com.tuoheng.model.dto.UserBaseInfoDto;
import com.tuoheng.service.impl.OidcUserInfoServiceImpl;
@@ -10,30 +9,24 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.sql.DataSource;
import java.util.Collections;
import java.util.function.Function;

/**
@@ -81,7 +74,7 @@ public class SecurityConfig {
.and()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/toLogin"))
.accessDeniedHandler(new AccessDeniedHandler()))
//.authenticationEntryPoint(new AuthenticationEntryPoint()))
.apply(authorizationServerConfigurer)
@@ -92,9 +85,11 @@ public class SecurityConfig {
@Bean
@Order(2)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
//http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterAt(new VerifyCodeFilter(),UsernamePasswordAuthenticationFilter.class);
http.csrf().disable()
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/login", "/getHealth", "/static/**").permitAll()
.antMatchers("/toLogin", "/getHealth", "/static/**", "/vercode").permitAll()
.antMatchers("/user/create").permitAll()
.anyRequest().authenticated()
)
@@ -102,7 +97,7 @@ public class SecurityConfig {
// authorization server filter chain
//.formLogin(Customizer.withDefaults());
.formLogin(form ->
form.loginPage("/login")
form.loginPage("/toLogin")
.loginProcessingUrl("/login")
);


+ 81
- 0
tuoheng_oidc_server/src/main/java/com/tuoheng/config/VerifyCodeFilter.java View File

@@ -0,0 +1,81 @@
package com.tuoheng.config;

/**
* @author chenjiandong
* @description: TODO
* @date 2022/10/12 9:32
*/
import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.tuoheng.constants.CommonConstant;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;

public class VerifyCodeFilter extends AbstractAuthenticationProcessingFilter {
// 是否开启验证码功能
private boolean isOpenValidateCode = true;


public VerifyCodeFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
SimpleUrlAuthenticationFailureHandler failedHandler = (SimpleUrlAuthenticationFailureHandler)getFailureHandler();
failedHandler.setDefaultFailureUrl("/toLogin?validerror");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res=(HttpServletResponse)response;

if (!requiresAuthentication(req, res)) {
chain.doFilter(request, response);
return;
}
if (isOpenValidateCode) {
if(!checkValidateCode(req, res))return;
}
chain.doFilter(request,response);
}

/**
* 覆盖授权验证方法,这里可以做一些自己需要的session设置操作
*/
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
return null;
}

protected boolean checkValidateCode(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
HttpSession session = request.getSession();
String sessionValidateCode = obtainSessionValidateCode(session);
// 让上一次的验证码失效
session.setAttribute(CommonConstant.VALIDATE_CODE, null);
String validateCodeParameter = obtainValidateCodeParameter(request);
if (StringUtils.isEmpty(validateCodeParameter) || !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {
unsuccessfulAuthentication(request, response, new InsufficientAuthenticationException("输入的验证码不正确"));
return false;
}
return true;
}

private String obtainValidateCodeParameter(HttpServletRequest request) {
Object obj = request.getParameter(CommonConstant.VALIDATE_CODE);
return null == obj ? "" : obj.toString();
}

protected String obtainSessionValidateCode(HttpSession session) {
Object obj = session.getAttribute(CommonConstant.VALIDATE_CODE);
return null == obj ? "" : obj.toString();
}
}

+ 11
- 0
tuoheng_oidc_server/src/main/java/com/tuoheng/constants/CommonConstant.java View File

@@ -0,0 +1,11 @@
package com.tuoheng.constants;

/**
* 安全配置常量
*/
public class CommonConstant {

public static final String VALIDATE_CODE = "validateCode";


}

+ 3
- 0
tuoheng_oidc_server/src/main/java/com/tuoheng/controller/HealthController.java View File

@@ -1,5 +1,6 @@
package com.tuoheng.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@@ -9,10 +10,12 @@ import org.springframework.web.bind.annotation.RestController;
* @date 2022/9/28 9:55
*/
@RestController
@Slf4j
public class HealthController {

@GetMapping("/getHealth")
public String getHealth(){
log.info("oidc is ok");
return "oidc is ok";
}


+ 17
- 2
tuoheng_oidc_server/src/main/java/com/tuoheng/controller/Oauth2Controller.java View File

@@ -2,7 +2,9 @@ package com.tuoheng.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
* @author chenjiandong
@@ -12,9 +14,22 @@ import org.springframework.web.bind.annotation.GetMapping;
@Slf4j
@Controller
public class Oauth2Controller {
@GetMapping("login")
public String login() {

@GetMapping("toLogin")
public String login(@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "validerror", required = false) String validerror,
@RequestParam(value = "logout", required = false) String logout, Model model) {
if (error != null) {
model.addAttribute("msg", "用户名或密码错误!");
}
if(validerror!=null){
model.addAttribute("msg", "验证码错误!");
}
if (logout != null) {
model.addAttribute("msg", "成功退出!");
}
return "login";
}


}

+ 30
- 0
tuoheng_oidc_server/src/main/java/com/tuoheng/controller/VerifyCodeController.java View File

@@ -0,0 +1,30 @@
package com.tuoheng.controller;

import com.tuoheng.until.VerifyCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
* @author chenjiandong
* @description: TODO
* @date 2022/10/11 17:08
*/
@RestController
public class VerifyCodeController {

@GetMapping("/vercode")
public void code(HttpServletRequest req, HttpServletResponse resp) throws IOException {
VerifyCode vc = new VerifyCode();
BufferedImage image = vc.getImage();
String text = vc.getText();
HttpSession session = req.getSession();
session.setAttribute("validateCode", text);
VerifyCode.output(image, resp.getOutputStream());
}
}

+ 114
- 0
tuoheng_oidc_server/src/main/java/com/tuoheng/until/VerifyCode.java View File

@@ -0,0 +1,114 @@
package com.tuoheng.until;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;

/**
* @author chenjiandong
* @description: TODO
* @date 2022/10/11 17:07
*/
public class VerifyCode {

private int width = 100;// 生成验证码图片的宽度
private int height = 50;// 生成验证码图片的高度
private String[] fontNames = { "宋体", "楷体", "隶书", "微软雅黑" };
private Color bgColor = new Color(255, 255, 255);// 定义验证码图片的背景颜色为白色
private Random random = new Random();
private String codes = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ";
private String text;// 记录随机字符串

/**
* 获取一个随意颜色
*
* @return
*/
private Color randomColor() {
int red = random.nextInt(150);
int green = random.nextInt(150);
int blue = random.nextInt(150);
return new Color(red, green, blue);
}

/**
* 获取一个随机字体
*
* @return
*/
private Font randomFont() {
String name = fontNames[random.nextInt(fontNames.length)];
int style = random.nextInt(4);
int size = random.nextInt(5) + 24;
return new Font(name, style, size);
}

/**
* 获取一个随机字符
*
* @return
*/
private char randomChar() {
return codes.charAt(random.nextInt(codes.length()));
}

/**
* 创建一个空白的BufferedImage对象
*
* @return
*/
private BufferedImage createImage() {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
g2.setColor(bgColor);// 设置验证码图片的背景颜色
g2.fillRect(0, 0, width, height);
return image;
}

public BufferedImage getImage() {
BufferedImage image = createImage();
Graphics2D g2 = (Graphics2D) image.getGraphics();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
String s = randomChar() + "";
sb.append(s);
g2.setColor(randomColor());
g2.setFont(randomFont());
float x = i * width * 1.0f / 4;
g2.drawString(s, x, height - 15);
}
this.text = sb.toString();
drawLine(image);
return image;
}

/**
* 绘制干扰线
*
* @param image
*/
private void drawLine(BufferedImage image) {
Graphics2D g2 = (Graphics2D) image.getGraphics();
int num = 5;
for (int i = 0; i < num; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
g2.setColor(randomColor());
g2.setStroke(new BasicStroke(1.5f));
g2.drawLine(x1, y1, x2, y2);
}
}

public String getText() {
return text;
}

public static void output(BufferedImage image, OutputStream out) throws IOException {
ImageIO.write(image, "JPEG", out);
}
}

+ 2
- 2
tuoheng_oidc_server/src/main/resources/logback.xml View File

@@ -13,8 +13,8 @@
<contextName>tuoheng_oidc_server</contextName>

<!--定义日志变量-->
<!--<property name="logging.path" value="D:\\idealogs\\tuoheng_oidc"/>-->
<property name="logging.path" value="/data/java/logs/tuoheng_oidc"/>
<property name="logging.path" value="D:\\idealogs\\tuoheng_oidc"/>
<!--<property name="logging.path" value="/data/java/logs/tuoheng_oidc"/>-->
<!--日志格式: [时间] [级别] [线程] [行号] [logger信息] - [日志信息]-->
<property name="logging.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%level][%thread][%L] %logger - %msg%n"/>
<property name="logging.charset" value="UTF-8"/>

+ 29
- 1
tuoheng_oidc_server/src/main/resources/templates/login.html View File

@@ -76,6 +76,21 @@
color: #FFFFFF;
font-size: 16px;
}
.form__code{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 18px;
}
.form__code input{
width: calc(100% - 152px);
margin-bottom: 0;
}
.form__code img{
width: 100px;
height: 42px;
cursor: pointer;
}
.login__form .form__tips{
margin: 0;
height: 0;
@@ -95,13 +110,26 @@
<form th:action="@{/login}" method="post">
<input name="username" placeholder="请输入用户名" type="text"/>
<input name="password" placeholder="请输入密码" type="password"/>
<div class="form__code">
<input name="validateCode" placeholder="请输入验证码"/>
<img class="code__img" src="/vercode" />
</div>
<div class="form__tips is--error" th:if="${param.error}">
用户名密码错误,请重新输入!
</div>
<!-- <input name="code" placeholder="请输入验证码" value="code" /> -->
<div class="form__tips is--error" th:if="${param.validerror}">
验证码错误,请重新输入!
</div>
<button type="submit">登 录</button>
</form>
</div>
</div>
<!--绑定点击事件 -->
<script>
const imgDom = document.querySelector('.code__img')
imgDom.onclick = function() {
imgDom.src='/vercode'
}
</script>
</body>
</html>

Loading…
Cancel
Save