SpringBoot+SpringSecurity+mysql realizes authentication and authorization

One, Spring Security framework

1. Introduction to the Framework

        Official introduction: Spring Security is a powerful and highly customizable authentication and access control framework. It is the de facto standard for protecting Spring-based applications. Spring Security is a framework that focuses on providing authentication and authorization for Java applications. Like all Spring projects, the real power of Spring Security is that it can be easily extended to meet custom requirements.

        Spring Security is a security framework that provides a declarative security access control solution for Spring-based enterprise application systems (in short, it controls access permissions). Application security includes user authentication (Authentication) and user authorization (Authorization) Two parts. The main core functions of Spring Security are authentication and authorization, and all architectures are also implemented based on these two core functions.

        User authentication refers to verifying whether a user is a legal subject in the system, that is, whether the user can access the system. User authentication generally requires the user to provide a user name and password. The system completes the authentication process by verifying the user name and password.

        User authorization refers to verifying whether a user has the authority to perform a certain operation. In a system, different users have different permissions. For example, for a file, some users can only read it, and some users can modify it. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions.

feature:

  • Comprehensive and scalable support for authentication and authorization
  • Prevent attacks such as session fixation, clickjacking, and cross-site request forgery
  • Servlet API integration
  • Optional integration with Spring Web MVC

2. Framework Principle

        The best way to protect Web resources is Filter, and the best way to protect method calls is AOP. Therefore, when we authenticate users and grant permissions, Spring Security uses various interceptors to control access to permissions to achieve security.

The main filters of the Spring Security framework (Filter):

The best way to protect Web resources is Filter, and the best way to protect method calls is AOP. Therefore, when we authenticate users and grant permissions, Spring Security uses various interceptors to control access to permissions to achieve security.

The main filters of the Spring Security framework (Filter): 

  • WebAsyncManagerIntegrationFilter 
  • SecurityContextPersistenceFilter 
  • HeaderWriterFilter 
  • CorsFilter 
  • LogoutFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • AnonymousAuthenticationFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • UsernamePasswordAuthenticationFilter
  • BasicAuthenticationFilter

  The core components of the Spring Security framework:

  • SecurityContextHolder: Provides access to SecurityContext
  • SecurityContext: Holds the Authentication object and other information that may be needed
  • AuthenticationManager which can contain multiple AuthenticationProviders
  • The ProviderManager object is the implementation class of the AuthenticationManager interface
  • AuthenticationProvider is mainly used for authentication operations. The class calls the authenticate() method to perform authentication operations.
  • Authentication: Authentication subject in Spring Security mode
  • GrantedAuthority: Application-level authorization for authentication topics, including the authorization information of the current user, usually represented by roles
  • UserDetails: the necessary information to construct the Authentication object, which can be customized, and may need to be obtained by accessing the DB
  • UserDetailsService: Construct UserDetails object through username, and obtain UserDetail object according to userName through loadUserByUsername (you can customize implementation based on your own business here, such as through database, xml, cache, etc.)  

Two, SpringBoot integrates Spring Security

1. Project Environment

(1) JDK version: 1.8

(2)Spring Boot:2.1.2.RELEASE

(3)Spring Security 5.1.3

(4)IntelliJ IDEA 2016.3.4

2. Build database and table

The table creation statement is as follows, the password corresponding to the user has been encrypted with md5.

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `userName` varchar(255) DEFAULT NULL COMMENT '姓名',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `roles` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户表';

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'ouyang', 'bbd126f4856c57f68d4e30264da6a4e6', 'ROLE_ADMIN,ROLE_USER');
INSERT INTO `user` VALUES ('2', 'admin', 'bbd126f4856c57f68d4e30264da6a4e6', 'ROLE_ADMIN');
INSERT INTO `user` VALUES ('3', 'user', 'bbd126f4856c57f68d4e30264da6a4e6', 'ROLE_USER');

3. Add dependencies and configure yml

Depend on 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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.oycbest</groupId>
    <artifactId>springsecuritytest</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    <name>springsecuritytest</name>
    <description>springsecurity 安全认证框架实战项目</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>


        <!--Spring Boot热加载 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!--Spring Security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--JDBC-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.29</version>
        </dependency>
        <!--spring-data-jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-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>

application.yml placement:

server:
  port: 8082

spring:
    datasource:
        url: jdbc:mysql://127.0.0.1:3306/springsecurity?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8
        username: ouyangcheng
        password: 123456
        driver-class-name: com.mysql.jdbc.Driver
        druid:
            initialSize: 1
            minIdle: 1
            maxActive: 50
            maxWait: 6000
            timeBetweenEvictionRunsMillis: 6000
            minEvictableIdleTimeMillis: 30000
            testWhileIdle: true
            testOnBorrow: true
            testOnReturn: true
            validationQuery: SELECT 1 from dual
            connectionProperties: config.decrypt=false

4. Create spring security configuration file

package com.oycbest.config;

import com.oycbest.domain.User;
import com.oycbest.service.PasswordEncoder;
import com.oycbest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.Resource;

/**
 * @Author: oyc
 * @Date: 2019/1/29 13:45
 * @Description:
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserService<User> userService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //允许基于HttpServletRequest使用限制访问
        http.authorizeRequests()
                //不需要身份认证
                .antMatchers("/", "/home","/toLogin","/**/customer/**").permitAll()
                .antMatchers("/js/**", "/css/**", "/images/**", "/fronts/**", "/doc/**", "/toLogin").permitAll()
                .antMatchers("/user/**").hasAnyRole("USER")
                //.hasIpAddress()//读取配置权限配置
                .antMatchers("/**").access("hasRole('ADMIN')")
                .anyRequest().authenticated()
                //自定义登录界面
                .and().formLogin().loginPage("/toLogin").loginProcessingUrl("/login").failureUrl("/toLogin?error").permitAll()
                .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .and().exceptionHandling().accessDeniedPage("/toLogin?deny")
                .and().httpBasic()
                .and().sessionManagement().invalidSessionUrl("/toLogin")
                .and().csrf().disable();
    }
}

5. Create UserService to implement UserDetialsServer class

        This class mainly implements the loadUserByname method, and then we can inject our service, repository or mapper interface into this class, and then obtain the user according to the username inside the method, and then obtain the user's permissions. Query the database through the user name. If the user is not found, the login fails and stays on the original web page. If the user is found, the user's account password and all the permissions of the found user are encapsulated into the security user Returned in the class (the obtained form is org.springframework.security.core.userdetails.User@c41325a7: Username: ouyang; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN, ROLE_USER), then security will compare the password sent from the front desk with the password found to see if the login is successful, and the failure stays on the original web page. This step is solved by the framework itself, no need for us to judge, just Return to User.

        I directly inject UserRepository here, and use UserRepository to directly obtain user information and permissions. If you have questions about UserRepository, please refer to the previous section of Spring Boot Introduction Series 8 (SpringBoot integrates SpringData JPA to operate Mysql database): https://blog.csdn.net/u014553029 /article/details/86662518

package com.oycbest.service;

import com.oycbest.domain.User;
import com.oycbest.repository.UserRepository;
import org.apache.commons.lang3.StringUtils;
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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: oyc
 * @Date: 2019/1/29 14:19
 * @Description: 用户服务类
 */
@Service
public class UserService<T extends User> implements UserDetailsService {

    @Resource
    private UserRepository<User> repository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            User user = repository.findByUserName(username);
            if (user == null) {
                throw new UsernameNotFoundException("用户名不存在");
            }
            //用户权限
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            if (StringUtils.isNotBlank(user.getRoles())) {
                String[] roles = user.getRoles().split(",");
                for (String role : roles) {
                    if (StringUtils.isNotBlank(role)) {
                        authorities.add(new SimpleGrantedAuthority(role.trim()));
                    }
                }
            }
            return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), authorities);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

6. Other

The login.html, MD5Util and PasswordEncoder used, the code is as follows:

login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}" style="color: red;">
    Invalid username and password.
</div>
<div th:if="${param.logout}" style="color: red;">
    You have been logged out.
</div>
<form th:action="@{/login}" method="post">
    <div><label> User Name : <input type="text" name="username"/> </label></div>
    <div><label> Password: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="submit"/> <input type="reset" value="reset"/></div>
</form>
<p>Click <a th:href="@{/home}">here</a> go to home page.</p>
</body>
</html>

MD5Util.java

package com.oycbest.util;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @Author: oyc
 * @Date: 2018/12/3 11:11
 * @Description: MD5加密工具
 */
public class MD5Util {

    public static final int time = 5;

    public static final String SALT = "springsecurity";

    /**
     * 密码加密方法
     *
     * @param password
     * @return
     */
    public static String encode(String password) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("MD5 algorithm not available.  Fatal (should be in the JDK).");
        }
        try {
            for (int i = 0; i < time; i++) {
                byte[] bytes = digest.digest((password + SALT).getBytes("UTF-8"));
                password = String.format("%032x", new BigInteger(1, bytes));
            }
            return password;
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("UTF-8 encoding not available.  Fatal (should be in the JDK).");
        }
    }

    public static void main(String[] args) {
        System.out.println(MD5Util.encode("123456"));
    }
}

PasswordEncoder.java

package com.oycbest.service;

import com.oycbest.util.MD5Util;

/**
 * @Author: oyc
 * @Date: 2018/12/3 10:29
 * @Description: 密码加密类
 */
public class PasswordEncoder implements org.springframework.security.crypto.password.PasswordEncoder {

    @Override
    public String encode(CharSequence rawPassword) {
        return MD5Util.encode((String) rawPassword);
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {//user Details Service验证
        return encodedPassword.equals(MD5Util.encode((String) rawPassword));
    }

}

Three, test

        First log in with user ouyang, because ouayng has all permissions, so you can access admin and user; when you log in with admin user, you can access admin normally, but when you access the user path, it will be blocked because the admin user does not have access The permissions of the user path.

UserService中的loadUserByUsername在认证过程获取数据如下:

Source address: https://github.com/oycyqr/SpringSecurity

Guess you like

Origin blog.csdn.net/u014553029/article/details/86690622