SpringSecurity
这个是Spring提供的一个用于防止常见攻击,权限校验以及授权的一个安全框架
本篇文章是教你如何快速的上手SpringSecurity基础,更深的使用教程还没编写,因为集合框架那一篇还没搞完等集合框架研究完了在接着往后整这个,今天先水一波,等明天了在发HashSet的手写教程
开始使用
版本
IDEA 2020
Java 1.8
Springboot 2.4.0
搭建项目
使用Spring initializr 搭建项目
Security默认配置
默认配置就是你项目创建好后,只要引入了安全框架的依赖后,就默认启动了
项目创建好后,我们啥也不动,就直接运行,然后随便访问一个地址(http://localhost:8080),看看会不会跳转到登录页面
然后会看到一个登录表单的页面,我们是刚创建的项目,连controller类都没创建,那这个页面怎么出来的?这个就是spring security自己带的一个表单页面,因为我们并没有给它任何配置。
当我们运行时,控制台会输出这个,这个就是默认生成的登录密码,用户名是user
Using generated security password: 4a0ccd30-3d11-46a5-8ecb-aa8a6056e6e5
接下来我们试着登录一下。
登录成功后会返回我们登录前跳转的那个网址,因为我们没有任何controller,所以就是404,但是我们登录成功了
这个时候Spring security已经是部署好了,那么如何用我们自己的表单呢??
创建WebSecurityConfig类
继承 WebSecurityConfigurerAdapter类
package com.example.security;
import org.springframework.beans.factory.annotation.Configurable;
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;
@Configurable
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login") //设置登录页面
.defaultSuccessUrl("/index") // 设置登录成功后跳转的页面
.permitAll()
.and()
.logout().permitAll();
http.csrf().disable(); //关闭csrf跨域
}
}
创建login.html与index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录成功</title>
</head>
<body>
登录成功后跳转页面
</body>
</html>
login.html (直接copy的它自带的那个表单的,改了几个字)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Please sign in</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
<div class="container">
<form class="form-signin" method="post" action="/login">
<h2 class="form-signin-heading">登录我们的程序</h2>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
</form>
</div>
</body></html>
创建Controller: TestController
创建一个 com.example.security.controller包
package com.example.security.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/index")
public String index(){
return "index";
}
}
接下来我们访问localhost:8080/index,来试一下,看看它登录页面长啥样
可以看到,页面已经是我们自定义的那个了
修改默认的用户名和密码
默认的用户名一直是 user
密码则是在启动时输出在控制台的那个
我们打开配置文件 application.properties(或者是application.yml),我们给它添加两个配置
spring.security.user.name=admin
spring.security.user.password=123456
然后重启项目,可以看到控制台已经不输出密码了,我们使用自定义的账号密码测试可以登上去。
接下来该用来关联我们的数据库了
创建数据库
运行下面sql文件,直接创建好表和字段
/*
Navicat Premium Data Transfer
Source Server : localhost_3306
Source Server Type : MySQL
Source Server Version : 80011
Source Host : localhost:3306
Source Schema : liveuser
Target Server Type : MySQL
Target Server Version : 80011
File Encoding : 65001
Date: 27/11/2020 18:42:16
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for li_role
-- ----------------------------
DROP TABLE IF EXISTS `li_role`;
CREATE TABLE `li_role` (
`id` int(11) NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for li_user
-- ----------------------------
DROP TABLE IF EXISTS `li_user`;
CREATE TABLE `li_user` (
`id` int(11) NOT NULL,
`user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pass` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`role` int(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
-- 添加权限
INSERT INTO `liveuser`.`li_role`(`id`, `name`) VALUES (1, 'ROLE_ADMIN')
INSERT INTO `liveuser`.`li_role`(`id`, `name`) VALUES (2, 'ROLE_USER')
-- 添加用户
INSERT INTO `li_user`(`id`, `user`, `pass`, `role`) VALUES (1, 'admin', '12345', 1)
INSERT INTO `li_user`(`id`, `user`, `pass`, `role`) VALUES (2, 'user', '12345', 2)
俩表没有外键也没有主键自增,有需要可以自己添加下
权限表(li_role)中权限名称前 必须要加 “ROLE_” ,这个是Security要求的
设计SQL语句
数据库创建完后,我们该思考一下需要什么sql
因为我们只写登录,所以我们就一个功能
SELECT U.ID,U.USER,U.PASS,R.NAME ROLE FROM LI_USER U LEFT JOIN LI_ROLE R ON R.ID = U.ROLE WHERE U.USER = 'ADMIN'
这个语句可以直接将用户信息,还有权限名称一块查出来
项目链接数据库,并使用Mybatis
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.name=defaultDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/liveuser?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
#Mybatis配置
mybatis.mapper-locations=classpath:/mapper/*.xml
#开启Mybatis下划线命名转驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
创建用户实体类
package com.example.security.pojo;
public class User {
Integer id;
String user;
String pass;
String role;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", user='" + user + '\'' +
", pass='" + pass + '\'' +
", role='" + role + '\'' +
'}';
}
}
创建Dao
由于时间问题,这里我们使用select注解,就不创建mapper了
package com.example.security.dao;
import com.example.security.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface TestDao {
@Select("SELECT U.ID,U.USER,U.PASS,R.NAME ROLE FROM LI_USER U LEFT JOIN LI_ROLE R ON R.ID = U.ROLE WHERE U.USER = #{username}")
User login(@Param("username") String username);
}
创建CustomUserDetailsService类
package com.example.security;
import com.example.security.dao.TestDao;
import com.example.security.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
import org.springframework.jdbc.datasource.UserCredentialsDataSourceAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.ArrayList;
import java.util.Collection;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
TestDao testDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User us = testDao.login(username);
if(us == null){
throw new UsernameNotFoundException("用户查找失败");
}
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(us.getRole()));
//因为我们有user实体类了,下面返回创建的user不是我们的实体类,挂上包名区分
return new org.springframework.security.core.userdetails.User(username,us.getPass(),authorities);
}
修改WebSecurityConfig类
package com.example.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
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.crypto.password.PasswordEncoder;
import sun.security.util.Password;
@Configurable
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomUserDetailsService customUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(getPasswordEncoder());
}
public PasswordEncoder getPasswordEncoder(){
return new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
};
}
//---------------------------------------------------------------
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin()
.loginPage("/login")
.defaultSuccessUrl("/index").permitAll()
.and()
.logout().permitAll();
http.csrf().disable();
}
}
接下来就可以用我们数据库中的数据登录了
给路径加权限
@PreAuthorize("hasRole('ROLE_ADMIN')") //可以在路径上加这个注解,例如
@RequestMapping("/index")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String index(){
return "index";
}
上面是在路径上做权限,还有一种在配置类里面加权限
.antMatchers("/test").hasRole("ADMIN")
http.authorizeRequests()
.antMatchers("/test").hasRole("ADMIN") //添加权限
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.defaultSuccessUrl("/index").permitAll()
.and()
.logout().permitAll();
http.csrf().disable();
当我们给这个路径加入admin权限后,使用user登录会看到403,这说明没有权限
但是我们用admin账号登录后,则会显示登录
结束
本篇是入门篇,适合刚学到spring security的朋友看
如果有错的地方,希望各位大佬同行在评论区指正,知错就改,避免带歪看这篇文文章的人,谢谢
欢迎大家访问:http://www.fanxing.live