Series directory
SpringSecurity rights management system actual combat – one, project introduction and development environment preparation
SpringSecurity rights management system practice – two, log, interface documents and other implementation
SpringSecurity rights management system practice – three, the main page and interface implementation
SpringSecurity rights management system actual combat – four, integration of SpringSecurity (part 1)
SpringSecurity rights management system practice – five, integration of SpringSecurity (under)
SpringSecurity authority management system combat – six, SpringSecurity integration JWT
SpringSecurity rights management system practice – seven, deal with some problems
SpringSecurity rights management system practice – eight, AOP record user logs, abnormal logs
SpringSecurity rights management system actual combat – nine, data rights configuration
preface
The content of this article is so mixed that I don’t know how to choose the title.
Last time we set up the basic environment of my-SpringSecurity-Plus, this time we will implement the system log configuration, configure swagger interface documents, configure druid connection pool, etc
First, Banner replacement
For those of you who are new to this term and don’t know what a banner is, it’s actually the pattern that the console prints when you run the SpringBoot project, which is this thing down here.
____ ____ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \ / _ ` | \ \ \ \ \ \ / ___) | | _) | | | | | | | (_ | |))))There comes' | |. __ _ - | | | _ _ - | | | _ \ __, | / / / / = = = = = = = = = | _ | = = = = = = = = = = = = = = = | ___ / / _ / _ / _ / : : Spring the Boot: : (v2.3.1. RELEASE)Copy the code
SpringBoot supports custom banner patterns. Just put it in the right place and SpringBoot will automatically replace it for us. Spring Boot defaults to finding banners in the following order:
- Find the files banner.gif, banner.jpg, and banner.png in Classpath, whichever one you find first.
- Go to the Classpath to find banner.txt
- If none of the above is found, use the default SpringBootBanner
All we need to do is create a new banner. TXT file under SRC /main/resources, find a website that generates banners online, such as patorjk, and copy the generated text to our banner. TXT file. Start the project and view the console
Isn’t it cool that the banner of a well-known project looks like this
////////////////////////////////////////////////////////////////////
// _ooOoo_ //
// o8888888o //
// 88"."88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'there comes \ / / / /.'\ \ | | / / `. / / / / / \ \ | | | "| | | / / / / / / / / _ | | | | | - : - | | | | | - / / / / / | | \ \ \ / / / | | / / / / | \ _ |' '\---/' '| | / / / / \. - \ __ ` ` ___ - / - / / / / / ___ `.'/-- --\'.. ___ // //.""< `. ___ \ _ < | > _ / ___.'>'"". / / / / | | : ` - \ `; ` \ _ / `; . ` / - ` : | | / / / / \ \ ` - \ _ __ \ / __ _ /. - ` / / / / / / = = = = = = = = ` - ____ ` - ___ \ _____ / ___ - ` _____....'= = = = = = = = / / / / ` = - ='/ / / / ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ / / / / the Buddha bless Never goes down Never BUG / / ////////////////////////////////////////////////////////////////////Copy the code
Second, the log
In the development of a project, the log is an essential component to record events. Should be a lot of entry-level partners are not how much attention to the log, for me, even now I do not attach great importance to the log, also did not develop the habit of logging. However, logs are especially important in a system to help locate bugs quickly and ensure high availability of services.
The Spring Boot uses the LogBack log system by default. If you do not need to change it to another log system, such as Log4j2, you do not need to configure the LogBack log system. The LogBack logs are printed to the console by default.
Spring Boot projects usually refer to spring-boot-starter or spring-boot-starter-web dependencies, which include the spring-boot-starter-logging dependency. So we don’t need to change dependencies if we don’t use another logging framework.
If we want to use logging, we just need to annotate @slf4j (lambok plug-in required) on the corresponding class, and log. Indf (),log.error(), etc. Let’s make the HelloController look like this
@Controller
@Slf4j
public class HelloController {
@GetMapping(value = "/index")
public String index(a){
log.info("Test");
log.error("Test");
return "index";
}
@GetMapping(value = "/login")
public String login(a){
return "login";
}
@GetMapping(value = "/console/console1")
public String console1(a){
return "console/console1"; }}Copy the code
Restart the project, visit http://localhost:8080/index console will print the following information
So how do you store logs in a file? We just need to define it briefly in application.yml
logging:
file:
path: src\main\resources\logger\ The logger folder needs to be generated in advance
Copy the code
Starting the project will generate a spring.log file in the Logger directory with the same content as the console output.
The output format of the log can be customized, but the content of the console output after customization is not in color, of course, can be defined in color, as well as the size of the log file generation (not always stored in a file, that is, not forever) and the storage time, etc., can be customized. Here I do not introduce in detail, interested partners can understand their own.
Swagger interface document
Swagger is a canonical and complete framework for generating, describing, invoking, and visualizing RESTful Web services. The overall goal is to have clients and file systems update at the same rate as servers. File methods, parameters, and models are tightly integrated into server-side code, allowing the API to always be in sync. Swagger makes it easy to deploy management and use powerful apis. Official website: Swagger. IO /. Swagger can also be used to test interfaces (many people use Postman, but Swagger is probably easier to use)
So we first need to add dependencies in Maven
<! --swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<! --swagger ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Copy the code
I already have this dependency in the previous chapter, so don’t add it again, just for illustration.
Create a new Config package in the launch class hierarchy, and create a New SwaggerConfig class in it
@Configuration// indicates that this is a configuration class
@EnableSwagger2/ / open Swagger
public class SwaggerConfig {
@Bean
public Docket webApiConfig(a){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")/ / group name
.apiInfo(webApiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.codermy.myspringsecurityplus.controller"))// Scan the packet
.paths(PathSelectors.any())
.build();
}
/** * This set of API description, including author, introduction, version, etc. *@return* /
private ApiInfo webApiInfo(a){
return new ApiInfoBuilder()
.title("My - springsecurity - plus - API documentation")
.description("This document describes the my-Spring Security-Plus interface definition")
.version("1.0") .build(); }}Copy the code
Then we visit http://localhost:8080/swagger-ui.html
The name of the interface can also be customized, see Swagger common annotations for details
Let’s change the HelloController again
@Controller
@Slf4j
@api (tags = "Early tests will be deleted later ")
public class HelloController {
@GetMapping(value = "/index")
public String index(a){
return "index";
}
@GetMapping(value = "/login")
public String login(a){
return "login";
}
@GetMapping(value = "/console/console1")
@apiOperation (value = "forward console1 request ")
public String console1(a){
return "console/console1"; }}Copy the code
Restart the access
Four, main interface interface
Next we replace the user management, role management, and permission management interfaces with our own.
First we create a new class to unify the return data format, create a new utiils package, and create a new Result class in it
// The class that returns the result
@Data
public class Result<T> implements Serializable {
@apiModelProperty (value = "successful ")
private Boolean success;
@apiModelProperty (value = "return code ")
private Integer code;
@apiModelProperty (value = "return message ")
private String msg;
@apiModelProperty (value = "total ")
private Integer count;
@apiModelProperty (value = "return data ")
private List<T> data = new ArrayList<T>();
// Make the constructor private
private Result(a) {}
// Successful static method
public static Result ok(a) {
Result r = new Result();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMsg("Success");
return r;
}
// Static method fails
public static Result error(a) {
Result r = new Result();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMsg("Failure");
return r;
}
public Result success(Boolean success){
this.setSuccess(success);
return this;
}
public Result message(String message){
this.setMsg(message);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
public Result data(List<T> list){
this.data.addAll(list);
return this;
}
public Result count(Integer count){
this.count = count;
return this; }}Copy the code
Create a new ReslutCode interface to define common status codes
public interface ResultCode {
/** * Request t succeeded */
public static Integer SUCCESS = 200;
/** * The table request succeeded */
public static Integer TABLE_SUCCESS = 0;
/**
* 请求失败
*/
public static Integer ERROR = 201;
/** * The request has been accepted */
public static final Integer ACCEPTED = 202;
/** * The operation was successfully performed, but no data is returned */
public static final Integer NO_CONTENT = 204;
/** * Resources have been removed */
public static final Integer MOVED_PERM = 301;
/** * redirects */
public static final Integer SEE_OTHER = 303;
/** * The resource is not modified */
public static final Integer NOT_MODIFIED = 304;
/** * Argument list error (missing, format mismatch) */
public static final Integer BAD_REQUEST = 400;
/** * not authorized */
public static final Integer UNAUTHORIZED = 401;
/** * Access restricted, authorization expired */
public static final Integer FORBIDDEN = 403;
/** * service not found */
public static final Integer NOT_FOUND = 404;
/** * disallowed HTTP methods */
public static final Integer BAD_METHOD = 405;
/** * Resources conflict, or resources are locked */
public static final Integer CONFLICT = 409;
/** * Unsupported data, media type */
public static final Integer UNSUPPORTED_TYPE = 415;
/** * The interface is not implemented */
public static final Integer NOT_IMPLEMENTED = 501;
}
Copy the code
Custom exception handling (here but more explanation, only a simple implementation of direct paste code
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// What exception is specified to handle
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.error().message("Global exception performed");
}
// Custom exception
@ExceptionHandler(MyException.class)
@ResponseBody
public Result error(MyException e){
log.error(e.getMessage());
e.printStackTrace();
returnResult.error().code(e.getCode()).message(e.getMsg()); }}Copy the code
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyException extends RuntimeException {
private Integer code;/ / status code
private String msg;// Exception information
}
Copy the code
Create a New PageTableRequest paging utility class
@Data
public class PageTableRequest implements Serializable {
private Integer page;/ / the initial page
private Integer limit;// A page of several data
private Integer offset;/ / page
public void countOffset(a){
if(null= =this.page || null= =this.limit){
this.offset = 0;
return;
}
this.offset = (this.page - 1) * limit; }}Copy the code
Let’s get down to business
Since I’m using Druid’s connection pool here (more on that later), I’ll post application.yml directly
server:
port: 8080
spring:
profiles:
active: dev
application:
name: my-springsecurity-plus
datasource:
driver:
driver-class-name: com.mysql.cj.jdbc.Driver
Don't forget if you are mysql8.0 or higher
url: jdbc:mysql://localhost:3306/my-springsecurity-plus? serverTimezone=Asia/Shanghai
username: root
password: 180430121
type: com.alibaba.druid.pool.DruidDataSource #druid connection pool will be explained later
druid:
Initial configuration
initial-size: 3
# Minimum number of connections
min-idle: 3
# Maximum number of connections
max-active: 15
Get connection timeout
max-wait: 5000
# Connection validity detection time
time-between-eviction-runs-millis: 90000
# Maximum free time
min-evictable-idle-time-millis: 1800000
test-while-idle: true
test-on-borrow: false
test-on-return: false
validation-query: select 1
Configure filters to monitor statistical intercepts
filters: stat
web-stat-filter:
url-pattern: / *
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
#StatViewServlet configuration, see Druid Wiki, config _StatViewServlet configuration
stat-view-servlet:
enabled: true The default value is true
url-pattern: /druid/*
reset-enable: true
login-username: admin
login-password: admin
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# mybatis configuration
mybatis:
type-aliases-package: com.codermy.myspringsecurityplus.entity
mapper-locations: classpath:/mybatis-mappers/*
configuration:
map-underscore-to-camel-case: true
logging:
file:
path: src\main\resources\logger\ The logger folder needs to be generated in advance
Copy the code
User management menu interface, should have created the corresponding class, just take this interface as an example, the other two are the same
MyUser entity class
@Data
@EqualsAndHashCode(callSuper = true)
public class MyUser extends BaseEntity<Integer>{
private static final long serialVersionUID = -6525908145032868837L;
private String userName;
private String password;
private String nickName;
private String phone;
private String email;
private Integer status;
public interface Status {
int LOCKED = 0;
int VALID = 1; }}Copy the code
Create two new methods in the UserDao that will be used for paging
@Mapper
public interface UserDao {
// Paging to return all users
@Select("SELECT * FROM my_user t ORDER BY t.id LIMIT #{startPosition}, #{limit}")
List<MyUser> getAllUserByPage(@Param("startPosition")Integer startPosition,@Param("limit")Integer limit);
// Count all users
@Select("select count(*) from My_user")
Long countAllUser(a);
}
Copy the code
UserService and UserServiceImlpl
public interface UserService {
Result<MyUser> getAllUsersByPage(Integer startPosition, Integer limit);
}
Copy the code
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public Result<MyUser> getAllUsersByPage(Integer startPosition, Integer limit) {
return Result.ok().count(userDao.countAllUser().intValue()).data(userDao.getAllUserByPage(startPosition,limit)).code(ResultCode.TABLE_SUCCESS);
}
}
Copy the code
UserController
@Controller
@RequestMapping("/api/user")
@API (tags = "User related Interface ")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
@ResponseBody
@apiOperation (value = "user list ")
public Result<MyUser> index(PageTableRequest pageTableRequest){
pageTableRequest.countOffset();
returnuserService.getAllUsersByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit()); }}Copy the code
We can compare the JSON he needs (user.json, in admin/data/user.json) with the JSON format we return
Instead of setting the null value, the description does not need to be used, and then in usr.
<! DOCTYPEhtml>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" th:href="@{/PearAdmin/component/layui/css/layui.css}" />
<link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pearCommon.css}"/>
</head>
<body class="pear-container">
<div class="layui-card">
<div class="layui-card-body">
<form class="layui-form" action="">
<div class="layui-form-item">
<label class="layui-form-label">The user name</label>
<div class="layui-input-inline">
<input type="text" name="nickName" placeholder="" class="layui-input">
</div>
<label class="layui-form-label">account</label>
<div class="layui-input-inline">
<input type="text" name="userName" placeholder="" class="layui-input">
</div>
<label class="layui-form-label">place</label>
<div class="layui-input-inline">
<select name="city" lay-verify="required">
<option value=""></option>
<option value="0">Beijing</option>
<option value="1">Shanghai</option>
<option value="2">Guangzhou</option>
<option value="3">shenzhen</option>
<option value="4">hangzhou</option>
</select>
</div>
<button class="pear-btn pear-btn-md pear-btn-primary" lay-submit lay-filter="user-query">
<i class="layui-icon layui-icon-search"></i>The query</button>
<button type="reset" class="pear-btn pear-btn-md">
<i class="layui-icon layui-icon-refresh"></i>reset</button>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="user-table" lay-filter="user-table"></table>
</div>
</div>
<script type="text/html" id="user-toolbar"></script>
<script type="text/html" id="user-bar"></script>
<script type="text/html" id="user-status"></script>
<script type="text/html" id="user-createTime"></script>
<script th:src="@{/PearAdmin/component/layui/layui.js}" charset="utf-8"></script>
<script>
layui.use(['table'.'form'.'jquery'].function () {
let table = layui.table;
let form = layui.form;
let $ = layui.jquery;
let MODULE_PATH = "operate/";
// The corresponding field must be the same as the name of the RETURNED JSON
let cols = [
[
{type:'checkbox'},
{title: 'account'.field: 'userName'.align:'center'.width:100},
{title: 'name'.field: 'nickName'.align:'center'},
{title: 'phone'.field: 'phone'.align:'center'},
{title: 'email'.field: 'email'.align:'center'},
{title: 'enable'.field: 'status'.align:'center'.templet:'#user-status'},
{title: 'Creation time'.field: 'createTime'.align:'center'.templet:'#user-createTime'},
{title: 'operation'.toolbar: '#user-bar'.align:'center'.width:130}
]
]
table.render({
elem: '#user-table'.url: '/api/user'.//+++++++++++ look at the url here. Replace the url of your interface with ++++++++++++++
page: true ,
cols: cols ,
skin: 'line'.toolbar: '#user-toolbar'.defaultToolbar: [{
layEvent: 'refresh'.icon: 'layui-icon-refresh',},'filter'.'print'.'exports']}); table.on('tool(user-table)'.function(obj){
if(obj.event === 'remove') {window.remove(obj);
} else if(obj.event === 'edit') {window.edit(obj); }}); table.on('toolbar(user-table)'.function(obj){
if(obj.event === 'add') {window.add();
} else if(obj.event === 'refresh') {window.refresh();
} else if(obj.event === 'batchRemove') {window.batchRemove(obj); }}); form.on('submit(user-query)'.function(data){
table.reload('user-table', {where:data.field})
return false;
});
form.on('switch(user-status)'.function(obj){
layer.tips(this.value + ' ' + this.name + ':'+ obj.elem.checked, obj.othis);
});
window.add = function(){
layer.open({
type: 2.title: 'new'.shade: 0.1.area: ['500px'.'400px'].content: MODULE_PATH + 'add.html'
});
}
window.edit = function(obj){
layer.open({
type: 2.title: 'change'.shade: 0.1.area: ['500px'.'400px'].content: MODULE_PATH + 'edit.html'
});
}
window.remove = function(obj){
layer.confirm('Make sure you want to delete this user', {icon: 3.title:'tip'}, function(index){
layer.close(index);
let loading = layer.load();
$.ajax({
url: MODULE_PATH+"remove/"+obj.data['id'].dataType:'json'.type:'delete'.success:function(result){
layer.close(loading);
if(result.success){
layer.msg(result.msg,{icon:1.time:1000},function(){
obj.del();
});
}else{
layer.msg(result.msg,{icon:2.time:1000}); }}})}); }window.batchRemove = function(obj){
let data = table.checkStatus(obj.config.id).data;
if(data.length === 0){
layer.msg("Unselected data", {icon:3.time:1000});
return false;
}
let ids = "";
for(let i = 0; i<data.length; i++){ ids += data[i].id+",";
}
ids = ids.substr(0,ids.length-1);
layer.confirm('Make sure you delete these users', {icon: 3.title:'tip'}, function(index){
layer.close(index);
let loading = layer.load();
$.ajax({
url: MODULE_PATH+"batchRemove/"+ids,
dataType:'json'.type:'delete'.success:function(result){
layer.close(loading);
if(result.success){
layer.msg(result.msg,{icon:1.time:1000},function(){
table.reload('user-table');
});
}else{
layer.msg(result.msg,{icon:2.time:1000}); }}})}); }window.refresh = function(param){
table.reload('user-table'); }})</script>
</body>
</html>
Copy the code
So when we click on User Management again, we’re accessing our own interface
When I read other people’s teaching blogs, I really want them to post all the code verbatim. When I wrote it myself, it made sense. The code took up too much space and affected the look and feel of the blog. So THE other two screens I’m going to supplement the code, just copy this.
I’ll show you two pictures, and I’ll show you what it looks like.
The database files and synchronization code are available from Gitee and Github
Druid connection pool
Druid is alibaba’s open source database connection pool. As a late comer, Druid has higher performance than DBCP and C3P0.
Of course Druid is much more than just a connection pool.
The advantages of the druid
- High performance. The performance is much higher than DBCP and C3P0.
- Druid supports any JDBC supported database. And Druid is optimized specifically for Oracle and mysql.
- Provides monitoring functions. You can monitor the execution time of SQL statements, the holding time of a ResultSet, the number of returned rows, the number of updated rows, the number of errors, and the error stack to learn about the connection pool and the working status of SQL statements, facilitating statistics and analysis of SQL execution performance
How to use?
Import dependencies, before the dependency to have no repeat import
<! --druid connection pool -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Copy the code
In the application. The yml configuration
spring:
profiles:
active: dev
application:
name: my-springsecurity-plus
datasource:
driver:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/my-springsecurity-plus? serverTimezone=Asia/Shanghai
username: root
password: 180430121
type: com.alibaba.druid.pool.DruidDataSource
druid:
Initial configuration
initial-size: 3
# Minimum number of connections
min-idle: 3
# Maximum number of connections
max-active: 15
Get connection timeout
max-wait: 5000
# Connection validity detection time
time-between-eviction-runs-millis: 90000
# Maximum free time
min-evictable-idle-time-millis: 1800000
test-while-idle: true
test-on-borrow: false
test-on-return: false
validation-query: select 1
Configure filters to monitor statistical intercepts
filters: stat
web-stat-filter:
url-pattern: / *
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
#StatViewServlet configuration, see Druid Wiki, config _StatViewServlet configuration
stat-view-servlet:
enabled: true The default value is true
url-pattern: /druid/*
reset-enable: true
login-username: admin # username
login-password: admin # your password
Copy the code
More detailed configurations are not covered here. Then restart project accesshttp://localhost:8080/druid/login.htmlEnter the user name and password to see the interface.Shout, finally finished another, write code when really did not feel so tired, like me this kind of writing bad often write to write their own disorderly…