SSO项目笔记
一、项目结构:
新建一个Maven项目,取名为HandsomeAq_SSO,它有4个子module,分别是:
auth:负责权限管理
gateway:负责网关管理
resource:负责资源管理
web:负责前端管理
在HandsomeAq_SSO的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>auth</module>
<module>resource</module>
<module>gateway</module>
<module>web</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hd</groupId>
<artifactId>HandSomeAq_SSO</artifactId>
<version>1.0.0</version>
<name>HandSomeAq_SSO</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--Spring Cloud 依赖(定义了微服务规范)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</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.5.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</project>
二、auth:
首先在auth中完成SSM的相关配置,并测试数据库的连通性。参考SSM配置的文章。
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>HandSomeAq_SSO</artifactId>
<groupId>com.hd</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>auth</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--spring整合mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</project>
以上配置成功后,开始配置权限管理;
在pom文件中添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
启动项目做测试:
命令行输入默认密码;
生成了默认的登录界面;
输入用户名:user和生成的默认密码进行测试。
测试成功后我们进行SE的配置。
创建config包和其下的SecurityConfig.java文件,使SecurityConfig.java继承WebSecurityConfigurerAdapter 并且重写
protected void configure(HttpSecurity auth) throws Exception {
}
用于定义认证规则和异常处理。
SecurityConfig:
package com.hd.config;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭跨域攻击
http
.csrf()
.disable();
http
// 处理登录请求
.formLogin();
// .successHandler()
// .failureHandler(null);
http
// 处理权限,即任何请求都需要认证
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
SE中有接口UserDetailsService来进行用户登录的验证,如果我们要用数据库中的信息进行登录,要实现UserDetailsService接口,并重写其中的方法:
package com.hd.service;
import com.hd.mapper.UserMapper;
import com.hd.pojo.User_Roles;
import com.hd.pojo.Users;
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;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users users = userMapper.FindUserByUsername(username);
if (users != null){
List<User_Roles> user_rolesList = users.getUser_roles();
StringBuffer buffer = new StringBuffer();
for (User_Roles user_roles : user_rolesList) {
String name = user_roles.getRoles().getName();
name = "Role_"+name;
buffer.append(name);
}
String Authority = new String(buffer);
List<GrantedAuthority> grantedAuthorityList =
AuthorityUtils.createAuthorityList(Authority);
User user = new User(username,users.getPassword(),grantedAuthorityList);
return user;
}
throw new RuntimeException("用户不存在");
}
}
顺便放上mapper.xml的配置文件用作参考:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hd.mapper.UserMapper">
<resultMap id="userMap" type="Users">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<collection property="user_roles" ofType="User_Roles">
<result property="roleId" column="role_id"/>
<collection property="roles" ofType="Roles">
<result property="name" column="name"/>
</collection>
</collection>
</resultMap>
<select id="FindUser" resultMap="userMap">
select u.id,u.username,u.`password`,r.`name` from
sys_users as u left join sys_user_roles as ur on u.id = ur.user_id
inner join sys_roles as r on ur.role_id = r.id;
</select>
<select id="FindUserByUsername" resultMap="userMap">
select u.id,u.username,u.`password`,r.`name` from
sys_users as u left join sys_user_roles as ur on u.id = ur.user_id
inner join sys_roles as r on ur.role_id = r.id where u.username = #{username};
</select>
</mapper>
<<注意>>在测试时要保证数据库中存储的密码是通过BCryptPasswordEncoder加密的,SE会将输入的密码通过BCryptPasswordEncoder后与数据库中的密码进行匹配.
启动项目进行测试。
要实现单点登录,我们需要生成一个token返回给客户,那么客户使用token就可以访问应用集群的任何一个子系统,而不需要重复的登录。
ticket和token是一回事,下面我们要通过用户的用户名,权限来生成token,并返回给客户:
编写两个工具类,WebUtil 用于传输用户信息。JwtUtil用来进行和token相关的操作,生成token,检验token。
JwtUtil:
package com.hd.utils;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
// 加密盐
private static String salt = "$VGH56876531419UHF$^";
public static String createToken(Map<String,Object> map) {
return Jwts.builder()
// 获取用户数据
.setClaims(map)
// 设置token生命周期
.setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
// 设置token的开始时间
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, salt)
.compact();
}
}
WebUtil:
package com.hd.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
public class WebUtils {
/**将数据以json格式写到客户端
* @param response 响应对象,负责向客户端输出数据的对象
* @param dataMap 封装了我们要向客户端输出的数据
* @throws IOException
*/
public static void writeJsonToClient(
HttpServletResponse response,
Map<String,Object> dataMap) throws IOException {
//1.设置响应数据的编码
response.setCharacterEncoding("utf-8");
//2.告诉浏览器响应数据的内容类型以及编码
response.setContentType("application/json;charset=utf-8");
//3.将数据转换为json格式字符串
String jsonStr= new ObjectMapper().writeValueAsString(dataMap);
//4.获取输出流对象将json数据写到客户端
//4.1获取输出流对象
PrintWriter out=response.getWriter();
//4.2通过输出流向网络的客户端写数据
out.println(jsonStr);
out.flush();
}
}
编写两个Handler,用于处理登录成功和登录失败的情况。
登录失败:
package com.hd.config.handler;
import com.hd.utils.WebUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class DefaultFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
Map<String,Object> map = new HashMap<>();
map.put("status", 500);
map.put("msg", "login unsuccess,please try again");
WebUtils.writeJsonToClient(response, map);
}
}
登录成功:
package com.hd.config.handler;
import com.hd.utils.JwtUtil;
import com.hd.utils.WebUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.util.*;
public class DefaultSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
User principal = (User)authentication.getPrincipal();
Map<String,Object> UserMap = new HashMap<>();
UserMap.put("username", principal.getUsername());
Collection<GrantedAuthority> authorities = principal.getAuthorities();
List AuthList = new ArrayList(authorities);
UserMap.put("authorities", AuthList);
String token = JwtUtil.createToken(UserMap);
Map<String,Object> map = new HashMap<>();
map.put("status", 200);
map.put("msg", "login success");
map.put("authorities", token);
WebUtils.writeJsonToClient(response, map);
}
}
修改SecurityConfig:
.formLogin()
.successHandler(new DefaultSuccessHandler())
.failureHandler(new DefaultFailureHandler());
打开postman进行测试:
到这里,auth部分的配置暂时告一段落。