本次整合实现的目标:1、SSO单点登录2、基于角色和spring security注解的权限控制。

  整合过程如下:

  1、使用maven构建项目,加入先关依赖,pom.xml如下:

<?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> <groupId>powerx.io</groupId>
<artifactId>springboot-security-jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>springboot-security-jwt</name>
<description>Demo project for Spring Boot</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

  2、JWT相关的两个过滤器:认证过滤器JWTLoginFilter和鉴权过滤器JWTAuthenticationFilter,为了简单,这里没有写专门的JWTUtils工具类来进行token的一系列操作,只是简单的利用JWT的第三方jar生成了token。

  JWTLoginFilter.java

package com.example.demo;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.stream.Collectors; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; /**
* 验证用户名密码正确后,生成一个token,并将token返回给客户端
* 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法 ,
* attemptAuthentication:接收并解析用户凭证。
* successfulAuthentication:用户成功登录后,这个方法会被调用,我们在这个方法里生成token并返回。
*
*/
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; public JWTLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
} @Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>())); } @Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) throws IOException, ServletException {
Claims claims = Jwts.claims();
claims.put("role", auth.getAuthorities().stream().map(s -> s.getAuthority()).collect(Collectors.toList()));
String token = Jwts.builder()
.setClaims(claims)
.setSubject(auth.getName())
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000))
.signWith(SignatureAlgorithm.HS512, "MyJwtSecret11").compact();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
String str = "{\"token\":\"" + token + "\"}";
PrintWriter out;
try {
out = response.getWriter();
out.print(str);
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

  JWTAuthenticationFilter.java

package com.example.demo;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
} /**
* 在拦截器中获取token并解析,拿到用户信息,放置到SecurityContextHolder,这样便完成了springsecurity和jwt的整合。
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (token != null) { Claims claims = Jwts.parser().setSigningKey("MyJwtSecret11").parseClaimsJws(token.replace("Bearer ", ""))
.getBody();
String user = claims.getSubject();
@SuppressWarnings("unchecked")
List<String> roles = claims.get("role", List.class);
List<SimpleGrantedAuthority> auth = roles.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()); if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, auth);
}
return null;
}
return null;
} }

  3、spring security基础配置和测试的controller。

  WebSecurityConfig.java

package com.example.demo;

import java.util.ArrayList;
import java.util.Collection; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private MyAuthenticationProvider provider;// 自定义的AuthenticationProvider @Bean
public PasswordEncoder myPasswordEncoder() {
return new MyPasswordEncoder();//自定义的加密工具
} @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login")
.and()
.csrf().disable()
.addFilter(new JWTLoginFilter(authenticationManager()))
.addFilter(new JWTAuthenticationFilter(authenticationManager()));
} @Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider);
auth.userDetailsService(userDetailsService());
} @Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager iud = new InMemoryUserDetailsManager();
Collection<GrantedAuthority> adminAuth = new ArrayList<>();
adminAuth.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
iud.createUser(new User("zhangsan", "123456", adminAuth));
return iud;
} }

  MyAuthenticationProvider.java

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component; @Component
public class MyAuthenticationProvider implements AuthenticationProvider { @Autowired
private UserDetailsService userDetailsService; /**
* 自定义验证方式
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserDetails user = userDetailsService.loadUserByUsername(username);
if(user.getPassword().equals(password)) {
return new UsernamePasswordAuthenticationToken(username, null, user.getAuthorities());
}
return null;
} @Override
public boolean supports(Class<?> arg0) {
return true;
} }

  MyPasswordEncoder.java

package com.example.demo;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
} @Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
}

  UserController.java

package com.example.demo;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
public class UserController { @RequestMapping("/hello")
public String hello() {
return "hello";
} @PreAuthorize("hasAuthority('ROLE_USER')")
@RequestMapping("/test1")
public String test1() {
return "test1";
}
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@RequestMapping("/test2")
public String test2() {
return "test2";
}
}

  至此,核心代码都以经贴上,启动项目,使用postman访问http://localhost:8099/login?username=zhangsan&password=123456,响应如下:

  我们访问http://localhost:8099/hello,同时在请求头上加入auth,如下:

  同样的url,不加auth则会给我们返回默认的登录页,如下:

  访问http://localhost:8099/test1,则提示权限不够,因为我们只为zhangsan用户分配了ROLE_ADMIN角色,而test1需要ROLE_USER。如下:

  访问http://localhost:8099/test2,如下:

  为了验证是否实现了单点登录,我们可以复制我们的项目,使用postman在第一个项目中拿到token,发现这个token在第二个项目中也可以使用,也就证明了我们的项目可以实现单点登录功能,在实际开发中,我们只需要在一个项目中去颁发token,而其他的项目中去按照相同的规则去解析这个token,这样就可以实现单点登录,需要注意的就是在解析token时的签名密码要一致或者对应。

  此外说一下,我在写demo中遇到的坑,第一个就是springboot2.0版本以上必须要有一个加密工具的实现,不然会报错;第二个就是在加入我们自己的过滤器后,必须使用默认的登录url即/login,不支持自定义的登录url了,具体原因我也不清楚;第三个就是在生成token时加入角色信息,不能直接加入auth.getAuthorities(),不然在解析时会出现类型不匹配,具体可参考我的代码。

springboot+security+JWT实现单点登录的更多相关文章

  1. springBoot整合spring security+JWT实现单点登录与权限管理--筑基中期

    写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...

  2. Spring Security OAuth2 SSO 单点登录

    基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...

  3. Spring Security构建Rest服务-1300-Spring Security OAuth开发APP认证框架之JWT实现单点登录

    基于JWT实现SSO 在淘宝( https://www.taobao.com )上点击登录,已经跳到了 https://login.taobao.com,这是又一个服务器.只要在淘宝登录了,就能直接访 ...

  4. spring boot:spring security+oauth2+sso+jwt实现单点登录(spring boot 2.3.3)

    一,sso的用途 ? 1,如果有多个应用系统,用户只需要登录一次就可以访问所有相互信任的应用系统. 不需要每次输入用户名称和用户密码, 也不需要创建并记忆多套用户名称和用户密码. 2,系统管理员只需维 ...

  5. 如何基于Security实现OIDC单点登录?

    一.说明 本文主要是给大家介绍 OIDC 的核心概念以及如何通过对 Spring Security 的授权码模式进行扩展来实现 OIDC 的单点登录. OIDC 是 OpenID Connect 的简 ...

  6. SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统

    一.单点登录SSO介绍   目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...

  7. Spring Security OAuth2实现单点登录

    1.概述 在本教程中,我们将讨论如何使用 Spring Security OAuth 和 Spring Boot 实现 SSO(单点登录). 本示例将使用到三个独立应用 一个授权服务器(中央认证机制) ...

  8. springboot+shiro+cas实现单点登录之cas server搭建

    CAS是YALE大学发起的一个开源项目,旨在为web应用系统提供一种可靠的单点登录方法.它主要分为client和server端,server端负责对用户的认证工作,client端负责处理对客户端受保护 ...

  9. 手把手教你学会 基于JWT的单点登录

      最近我们组要给负责的一个管理系统 A 集成另外一个系统 B,为了让用户使用更加便捷,避免多个系统重复登录,希望能够达到这样的效果--用户只需登录一次就能够在这两个系统中进行操作.很明显这就是单点登 ...

随机推荐

  1. linux mint gcc 编译第一个c程序

    GCC是Linux操作系统下一个非常重要的源代码编译工具,有着许多重要的选项,支持许多不同语言的编译,如C.C++.Ada. Fortran.Objective.Perl.Python.Ruby以及J ...

  2. 【[NOI2018]屠龙勇士】

    发现好像都是化掉系数之后套上\(ExCrt\)的板子 这好像是一个真正的扩展扩展中国剩余定理 我们要处理的方程是这样的形式 \[c_ix\equiv b_i(mod\ a_i)\] 其中\(c\)用一 ...

  3. flutter 配置环境

    1. 下载java SDK https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html c ...

  4. R中的apply族函数和多线程计算

    一.apply族函数 1.apply  应用于矩阵和数组 # apply # 1代表行,2代表列 # create a matrix of 10 rows x 2 columns m <- ma ...

  5. 将form表单元素的值序列化成对象

    /**jQuery * 将form表单元素的值序列化成对象 * @returns object */ var serializeObject = function(form) { var o = {} ...

  6. printf重定向问题

    1.使用printf库函数时,要加入头文件<stdio.h> 2.另外在keil里面需要把:use MicroLIB 勾选上,不然程序没办法在线调试.编译的时候不会报错. 3.当然可以不用 ...

  7. linux系统安装redis服务器与php redis扩展

    一 安装redis服务 1更新yum源 CentOS/RHEL 7.x: rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-lat ...

  8. Java中的集合框架-Collection(一)

    一,Collection接口 在日常的开发工作中,我们经常使用数组,但是数组是有很多的局限性的,比如:数组大小固定后不可修改,只能存储基本类型的值等等. 基于数组的这些局限性,Java框架就产生了用于 ...

  9. Win 10安装11g 客户端条件检查失败,INS-30131 无法从节点 "desktop-njm3a7m" 检索 exectask 的版本

    Win 10安装11g 客户端条件检查失败,[INS-30131] 执行安装程序验证所需的初始设置失败.无法从节点 "desktop-njm3a7m" 检索 exectask 的版 ...

  10. BZOJ 5248: [2018多省省队联测]一双木棋(对抗搜索)

    Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 439  Solved: 379[Submit][Status][Discuss] Descriptio ...