preface

As for what Spring Security is, there are two main roles, user authentication and authorization. In other words, the user can only perform other operations if he/she is logged in. If he/she is not logged in, he/she will be redirected to the login interface. Some users are authorized to perform an operation, while others are not authorized. It’s kind of a project security framework. Same as shiro framework. The difference between the two can be baidu small. Spring Security is a member of the Spring family, so Springboot is a natural support for Spring Security.

That said, Spring Security’s notoriously complex configuration is made easier by SpringBoot. If we are just demo effects, we can do 0 configuration implementation.

Let’s take a look

Rely on

We introduce Spring Security’s statter in the POM.xml file

<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>Copy the code

test

Let’s look at the 0 configuration first. After introducing the dependency, we create a HelloController that looks like this:

@RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello world"; }}Copy the code

Then we start the project, direct access as we normally understand it

localhost:8080/hello Copy the code

Will return Hello World. But the result is a redirect to /login. The following interface comes with Spring Security.As you can see above, Spring Security already works. You cannot access the/Hello interface without logging in.

The default user name is user; The password will be printed on the console at the start of our project. It will be different every time and generated randomly.Let’s enter the password and try againHello world: Hello World: hello World: Hello World: Hello World: Hello World: Hello World Of course not, after the successful login, the information will be saved in the session, and when you log in again, it will pass the session verification, so that you can access it. When the session expires, after we manually clear it, we need to log in again. Let’s try it. Open the console and clear the JsessioniD from the cookies in the application.We then asked to try it out and found that after deleting it, we were back to the login screen.

Configure users and passwords

We used the default username and password above, but we definitely don’t do this, it just states that SpringBoot is fully integrated with Spring Security. Let’s start by simply configuring the username and password, because we probably won’t use it in practice. The reason to speak, so that we understand more comprehensive, but also for the following bedding.

Application. The properties in the configuration

First let’s make it simple. We can configure the username and password directly in application.properties. To replace the default username and password.

spring.security.user.name=quellanan
spring.security.user.password=123456
spring.security.user.roles=adminCopy the code

Set the user name, password, and role respectively. We only use user authentication for the time being, so it doesn’t matter if the role is set. So once we’ve configured this, let’s restart the project and try again on the interface.No problem, but it doesn’t work, and we don’t actually do that.

In-memory configuration

In memory configuration, is relatively complicated, we create a config package, the package created under SecurityConfig WebSecurityConfigurerAdapter class hierarchy

@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() PasswordEncoder (passwordEncoder()) // Specifies the encryption mode .withUser("qaz").password(passwordEncoder().encode("123456")).roles("admin") .and() .withUser("test").password(passwordEncoder().encode("123456")).roles("USER"); } @bean public PasswordEncoder PasswordEncoder () {// BCryptPasswordEncoder: Return new BCryptPasswordEncoder(); return BCryptPasswordEncoder(); }}Copy the code

Here we rewrite the configure (AuthenticationManagerBuilder auth) method, is to define the user configuration into memory. There is one problem that needs to be explained here, which is that the password needs to be encrypted with BCryptPasswordEncoder. If not encrypted, the project will start without error, but the login will prompt the account password error. Another problem is that if we configure it here, what we configured in application.peoperties will be invalid.

The above two methods, in fact, are not commonly used, we will not write in the actual project user information. It’s basically stored in a database. So let’s start with our most common patterns.

As a result of this kind, involving more, a separate title out, not in the secondary title inside.

Authenticate users from the database

Since the database is used, the project will naturally introduce the configuration of data, I use mysql and Mybatis. This is the directory structure of the whole project, first put out, you know, and then step by step.

To build libraries built table

Three simple tables, user,roles, and roles_user.Here is the SQL. Just do it

/* Date: 2017-12-26 18:36:12 */ CREATE DATABASE `quellanan` DEFAULT CHARACTER SET utf8; USE `quellanan`; SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for roles -- ---------------------------- DROP TABLE IF EXISTS `roles`; CREATE TABLE `roles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of roles -- ---------------------------- INSERT INTO `roles` VALUES ('1', 'Super administrator '); INSERT INTO 'roles' VALUES ('2',' user '); INSERT INTO 'roles' VALUES ('3', '3'); INSERT INTO 'roles' VALUES ('4',' 3 '); INSERT INTO 'roles' VALUES ('5',' 3'); -- ---------------------------- -- Table structure for roles_user -- ---------------------------- DROP TABLE IF EXISTS `roles_user`; CREATE TABLE `roles_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rid` int(11) DEFAULT '2', `uid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `rid` (`rid`), KEY `roles_user_ibfk_2` (`uid`), CONSTRAINT `roles_user_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `roles` (`id`), CONSTRAINT `roles_user_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=131 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of roles_user -- ---------------------------- INSERT INTO `roles_user` VALUES (' 1 ', '1', '1'); INSERT INTO `roles_user` VALUES ('2', '2', '2'); INSERT INTO `roles_user` VALUES ('3', '3', '3'); INSERT INTO `roles_user` VALUES ('4', '1', '4'); -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`;  CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(64) DEFAULT NULL, `nickname` varchar(64) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `enabled` tinyint(1) DEFAULT '1', `email` varchar(64) DEFAULT NULL, `userface` varchar(255) DEFAULT NULL, `regTime` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES ('1', 'quellanan', '', '$2a$10$Hv0YGLi/siOswCTP236MtOTWbClcM6rN1LCyqwfRmrwCJZqXHsj5a', '1', '[email protected]','', 'the 09:30:22 2017-12-08'); INSERT INTO `user` VALUES ('2', 'qaz', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', '[email protected]','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('3', 'wsx', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', '[email protected]','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('4', 'test', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', '[email protected]','', '2017-12-08 09:30:22'); SET FOREIGN_KEY_CHECKS=1;Copy the code

Pom.xml adds dependencies

We first add the following dependencies based on the original POM file.

<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.0 < / version > < / dependency > < the dependency > < groupId > org.apache.com mons < / groupId > <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>Copy the code

The first three are mysql and Mybatis dependencies. Lombok is a tool class plug-in.

Meanwhile, we need to modify the build in the POM file, otherwise our project may not find the XML file of Mybatis.

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>Copy the code

Configure the application. The properties

Spring. The datasource. The driver - class - name = com. Mysql. JDBC. Driver spring. The datasource. Url = JDBC: mysql: / / 127.0.0.1:3306 / quellanan? allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5Copy the code

Here if you want to print mybatis SQL log. You can add a mybatis-config. XML file in the same directory as application.properties

<? The XML version = "1.0" encoding = "utf-8"? > <! DOCTYPE configuration PUBLIC "- / / mybatis.org//DTD Config / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> </configuration>Copy the code

Add it to application.properties

mybatis.config-location=classpath:/mybatis-config.xmlCopy the code

entry

We create RoleEntry under the Entry package. The code is as follows:

@Getter
@Setter
public class RoleEntry {
    private Long id;
    private String name;
}
Copy the code

We’re creating UserEntry, but UserEntry is special because we need to use Spring Security. So here, UserEntry needs to implement UserDetails. The code is as follows:

@Setter @Getter public class UserEntry implements UserDetails { private Long id; private String username; private String password; private String nickname; private boolean enabled; private List<RoleEntry> roles; private String email; private String userface; private Timestamp regTime; @override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); for (RoleEntry role : roles) { authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())); } return authorities; } @override public String getPassword() {return password; } @override public String getUsername() {return username; } /** * Whether the user account has expired */ @override public Boolean isAccountNonExpired() {return true; } /** * Whether the user account is locked */ @override public Boolean isAccountNonLocked() {return true; } /** * Whether the user password has expired */ @override public Boolean isCredentialsNonExpired() {return true; } @override public Boolean isEnabled() {return enabled; }}Copy the code

As you can see, they’re basically overridden methods. It’s easy.

mapper

So I’ve put the XML file and the interface together, and you can also create a Mapper in Resources, where you can put the XML file. Mapper layer nothing to say, is mybatis some knowledge, we here talk about the code posted out.

RolesMapper

@Mapper
public interface RolesMapper {
    List<RoleEntry> getRolesByUid(Long uid);
}Copy the code

RolesMapper.xml

<? The 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.zlflovemm.security.mapper.RolesMapper"> <select id="getRolesByUid" parameterType="long" resultType="com.zlflovemm.security.entry.RoleEntry"> SELECT r.* FROM roles r,roles_user ru WHERE r.`id`=ru.`rid` AND ru.`uid`=#{uid} </select> </mapper>Copy the code

UserMapper

@Mapper
public interface UserMapper {
    UserEntry loadUserByUsername(@Param("username") String username);
}Copy the code

UserMapper.xml

<? The 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.zlflovemm.security.mapper.UserMapper"> <select id="loadUserByUsername" resultType="com.zlflovemm.security.entry.UserEntry"> SELECT * FROM user WHERE username=#{username} </select> </mapper>Copy the code

service

One thing to note at the Service layer is that we need to implement the UserDetailsService interface. Let’s start by creating a UserService that inherits UserDetailsService. Then create a UserServiceImpl to implement the UserService to implement the UserDetailsService. This is done to ensure a uniform level of project structure.

UserService

public interface UserService extends UserDetailsService {
}Copy the code

UserServiceImpl

@Service @Slf4j @Transactional public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Autowired RolesMapper rolesMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { UserEntry user = userMapper.loadUserByUsername(s); If (user == null) {// Avoid returning null, which returns a user object without any value. } / / query the role of user information, and return in the user List < RoleEntry > roles. = rolesMapper getRolesByUid (user) getId ()); user.setRoles(roles); return user; }}Copy the code

As you can see, the main purpose is to implement the loadUserByUsername method. In this method we loadUserByUsername and getRolesByUid are the methods we defined in Mapper to query database data.

SecurityConfig

This is all preparation, and the main purpose is to provide a Bean. With that done, let’s go back to SecurityConfig, where we actually need to modify very little now. We comment out methods that the user writes to memory. Query through the database.

@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @bean public PasswordEncoder PasswordEncoder () {// BCryptPasswordEncoder: Return new BCryptPasswordEncoder(); return BCryptPasswordEncoder(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService) .passwordEncoder(passwordEncoder()); PasswoldEncoder is used to encrypt the password. If the password of user is not encrypted, you can omit this method. Caution Use the encryption mode provided by Security. }}Copy the code

Can start SecurityConfig files and contrast, in fact you is more than a userService, then configure (AuthenticationManagerBuilder auth) is through the userService to check.

test

Well, actually, at this point, we’re done, we start the project, and we can see that it looks exactly the same as it did when we wrote it in memory.

filter

Thought that this is over, in fact, there is a little ha ha. Now all interfaces need to be logged in first to access, if not logged in, jump to the login interface. In fact we are sure there are some that can be accessed without authentication, such as the following static files or registered requests. So we still need to configure filtering.

It’s just as easy to override the configure(HttpSecurity HTTP) method in the SecurityConfig file. Here I refer directly to the official website.Spring. IO/guides/gs/s…

This configure(HttpSecurity) method defines which URL paths should and should not be protected. Specifically, the “/” and “/ home” paths are configured not to require any authentication. All other paths must be validated. After the user logs in successfully, they are redirected to the page that required authentication earlier. There is a custom “/ login” page named loginPage() that everyone can view.

We should comment out the loginPage(“/login”) in our code, otherwise we need to write our own login interface and request. We’re going to use the framework here.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/", "/hello").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            //.loginPage("/login")
            .permitAll()
            .and()
            .logout()
            .permitAll();
    }Copy the code

This configuration states that /hell and/requests are not blocked, and other requests need to be logged in to be accessed. To make it easier to see, let’s add two more methods to the HelloController

@RequestMapping("/hello2") public String hello2(){ return "hello adada"; } @RequestMapping("/") public String hello3(){ return " qazqeee"; }}Copy the code

Now let’s go ahead and see what happens.Prove that the filtering we have configured is effective.

One day

To this is almost over, in fact, there are a lot of knowledge points, not an article can finish, here is to throw jade, I hope to help you. I’ll be updating you as well

Ok, source code I uploaded to github github.com/QuellanAn/s…

Follow-up fuel ♡

Welcome to pay attention to personal public account “Programmers love yogurt”

Share all kinds of learning materials, including Java, Linux, big data, etc. Information includes video documents and source code, and share myself and deliver quality technical blog posts.

Be sure to follow and share if you like ❤