目录
2)搭建jdbc或者mybatis或者mybatis-plus环境
4)使用mybatisX 生成基本的service和mapper
5)创建一个类实现UserDetailsService接口 ,重写方法(根据用户名到数据库中查找用户对象)
6)创建一个LoginUser实现UserDetails接口,封装用户信息
权限控制
一、认证:是否登录成功。
二、授权:权限控制,授予权限、校验权限。
相关框架
一、shiro
Shiro 是 apache权限框架,较之 JAAS 和 Spring Security,Shiro 在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。 Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。
二、springsecurity
Spring Security 是 Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰
富的功能,社区资源也比Shiro丰富。
一般来说中大型的项目都是使用SpringSecurity来做安全框架。
小项目用Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。
SpringSecurity
springsecurity 和 其他组件的简单交互
前后端分离场景下,登录校验流程的核心思路:token。 Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生 成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无 需再次带上用户名和密码。
使用token机制的身份验证方法,在服务器端不需要存储用户的登录记录。
springsecurity使用流程
1、搭建环境实现默认用户名和密码登录
springboot环境+springweb+springsecurity权限jar包
访问目标方法时,自动跳转到/login接口,输入用户名user和默认密码,登录成功后,自动跳转到目标方法。
localhost:8080/login
2、使用数据库表中定义好的用户名和密码访问实现等值密码匹配
1)sql文件
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '用户名',
`nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '昵称',
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '密码',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
`email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`phonenumber` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
`avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
`user_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',
`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人的用户id',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`del_flag` int(11) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
INSERT INTO `sys_user` VALUES (1, '张三', '张三', '{noop}1234', '0', NULL, NULL, NULL, NULL, '1', NULL, NULL, NULL, NULL, 0);
2)搭建jdbc或者mybatis或者mybatis-plus环境
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
3)配置mybatis-plus环境
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springsecurity
username: root
password: 123456
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
type-aliases-package: com.hl.springsecurity01.domain
mapper-locations: classpath:/mapper/*.xml
4)使用mybatisX 生成基本的service和mapper
5)创建一个类实现UserDetailsService接口 ,重写方法(根据用户名到数据库中查找用户对象)
package com.hl.springsecurity01.security;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hl.springsecurity01.domain.SysUser;
import com.hl.springsecurity01.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
/*
根据用户名查找用户对象
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名,到数据库表中,查找用户对象
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("user_name", username);
List<SysUser> list = sysUserService.list(queryWrapper);
//判断用户是否存在
LoginUser user = null;
if(list != null && list.size() > 0){
SysUser sysUser = list.get(0);
//封装数据到UserDetails接口实现类对象中
user = new LoginUser(sysUser);
}
return user;
}
}
6)创建一个LoginUser实现UserDetails接口,封装用户信息
package com.hl.springsecurity01.security;
import com.hl.springsecurity01.domain.SysUser;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/*
创建类实现UsersDetail接口,存储当前登录成功的用户信息
*/
@Data
public class LoginUser implements UserDetails {
private SysUser sysUser;
public LoginUser() {
}
public LoginUser(SysUser sysUser) {
this.sysUser = sysUser;
}
//返回用户权限信息,返回权限列表
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
}
@Override
public String getPassword() {
return sysUser.getPassword();
}
@Override
public String getUsername() {
return sysUser.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
3、加密机制 自定义密码匹配器
1)创建加密对象
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/*
创建加密对象(密码匹配器对象)
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
2)使用测试类,生成临时密码,测试匹配方法
@SpringBootTest
class Springsecurity01ApplicationTests {
@Autowired
private PasswordEncoder passwordEncoder;
@Test
void contextLoads() {
String pwd = passwordEncoder.encode("1234");
System.out.println(pwd);
//$2a$10$DpNgEn0fiYStJJ/EoYxf7uhBOMnuqK/UdBKmexX5KrEVPLuGQ0LsS
//$2a$10$sLUel1CyTVY1kDWM5BwlNu1WMLMqlys00NIir0SiEgR0A5ItM1Vda
//比对密码
boolean flag1 = passwordEncoder.matches("1234", "$2a$10$DpNgEn0fiYStJJ/EoYxf7uhBOMnuqK/UdBKmexX5KrEVPLuGQ0LsS");
boolean flag2 = passwordEncoder.matches("1234", "$2a$10$sLUel1CyTVY1kDWM5BwlNu1WMLMqlys00NIir0SiEgR0A5ItM1Vda");
System.out.println(flag1);
System.out.println(flag2);
}
}
$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
├─┬─┼──┬─┼──────────────┼───────────────────────────────────
│ │ │ │ │
│ │ │ │ └─ 哈希值(31字节)
│ │ │ └─ 盐值(22字符,16字节)
│ │ └─ cost参数(12 → 2^12=4096轮迭代)
│ └─ 算法版本(2a)
└─ 固定前缀
$<算法版本>$<cost>$<salt><hash>
4、自定义登录接口
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@RequestMapping("/login")
public R login(String username, String password) throws AuthenticationException {
//调用service
return loginService.login(username, password);
}
}
public interface LoginService {
public R login(String username, String password) throws AuthenticationException;
}
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public R login(String username, String password) throws AuthenticationException {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username, password);
//调用认证提供器的认证方法,进行用户名,密码认证
Authentication authentication = authenticationManager.authenticate(token);
//根据返回值判断是否认证成功
if(authentication == null){
//认证失败
throw new AuthenticationException("用户名或者密码错误");
}
if(authentication.isAuthenticated()){//认证成功
//返回 code ,msg,token
return R.ok("token.............","认证成功");
}
return null;
}
}
package com.hl.springsecurity01.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/*
创建加密对象(密码匹配器对象)
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
将数据库密码字段换成生成的加密字段$2a$10$DpNgEn0fiYStJJ/EoYxf7uhBOMnuqK/UdBKmexX5KrEVPLuGQ0LsS
登录成功,看到json返回,失败,没有任何提示。
5、token字符串的生成
<!--生成token-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JwtUtil工具类
package com.hl.springsecurity01.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "test";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw字符串
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw字符串
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析jwt
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
/**
* 用于测试
*/
public static void main(String[] args) throws Exception {
// String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
// Claims claims = parseJWT(token);
// System.out.println(claims);
String token = createJWT("1");
String token1 = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJlM2UzMGE0ZjhhMGE0OTQ1ODNjMzZlZmNjMWQ3YzQ4YiIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTc1MTUzMjI0MiwiZXhwIjoxNzUxNTM1ODQyfQ.ALfU833lUe1bCRlsAwcDd_mwD5j3sCtFCO3Ue1E-WWw";
System.out.println(token);
Claims claims = parseJWT(token1);
System.out.println(claims);
}
}
package com.hl.springsecurity01.service.impl;
import com.hl.springsecurity01.domain.R;
import com.hl.springsecurity01.security.LoginUser;
import com.hl.springsecurity01.service.LoginService;
import com.hl.springsecurity01.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import javax.security.sasl.AuthenticationException;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public R login(String username, String password) throws AuthenticationException {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username, password);
//调用认证提供器的认证方法,进行用户名,密码认证
Authentication authentication = authenticationManager.authenticate(token);
//根据返回值判断是否认证成功
if(authentication == null){
//认证失败
throw new AuthenticationException("用户名或者密码错误");
}
if(authentication.isAuthenticated()){//认证成功
//获取用户身份 LoginUser
LoginUser user = (LoginUser) authentication.getPrincipal();
//获取用户id
Long id = user.getSysUser().getId();
//根据用户id,生成token
String token2 = JwtUtil.createJWT(id+"");
//返回 code ,msg,token
return R.ok(token2,"认证成功");
}
return null;
}
}