从零搭建 Spring Boot 后端项目(四)

简介

这一小节主要是,整合 Spring Security

步骤
  • 新建数据库与表

    Create DATABASE `backend_template`;
    USE backend_template;
    CREATE TABLE `user` (
    	`id` bigint(11) NOT NULL AUTO_INCREMENT,
        `username` varchar(255) NOT NULL,
        `password` varchar(255) NOT NULL,
        PRIMARY KEY (`id`)
    );
    CREATE TABLE `role` (
        `id` bigint(11) NOT NULL AUTO_INCREMENT,
        `name` varchar(255) NOT NULL,
        PRIMARY KEY (`id`)
    );
    CREATE TABLE `user_role` (
        `user_id` bigint(11) NOT NULL,
        `role_id` bigint(11) NOT NULL
    );
    CREATE TABLE `role_permission` (
        `role_id` bigint(11) NOT NULL,
        `permission_id` bigint(11) NOT NULL
    );
    CREATE TABLE `permission` (
        `id` bigint(11) NOT NULL AUTO_INCREMENT,
        `url` varchar(255) NOT NULL,
        `name` varchar(255) NOT NULL,
        `description` varchar(255) NULL,
        `pid` bigint(11) NOT NULL,
        PRIMARY KEY (`id`)
    );
    
    INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e');
    INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e');
    INSERT INTO role (id, name) VALUES (1,'USER');
    INSERT INTO role (id, name) VALUES (2,'ADMIN');
    INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/common','common',0);
    INSERT INTO permission (id, url, name, pid) VALUES (2,'/user/admin','admin',0);
    INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
    INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
    INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
    INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
    INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
    INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);
    
  • 修改application-dev.properties配置文件中连接数据库为backend_template,此时就可以把test数据库删了,因为第三篇还要连接所以之前没让删

    spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/backend_template?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    
  • pom.xml中新增以下两个依赖

            <!-- Spring Security -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
                <version>2.3.1.RELEASE</version>
            </dependency>
            
            <!-- thymeleaf -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
                <version>2.3.1.RELEASE</version>
            </dependency>
    
  • com.example.backend_template.entity下新建User类

    package com.example.backend_template.entity;
    
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * @ClassName User
     * @Description
     * @Author L
     * @Date Create by 2020/6/28
     */
    public class User implements UserDetails, Serializable {
    
        private Long id;
        private String username;
        private String password;
    
        private List<Role> authorities;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public List<Role> getAuthorities() {
            return authorities;
        }
    
        public void setAuthorities(List<Role> authorities) {
            this.authorities = authorities;
        }
    
        /**
         * 用户帐号是否未过期
         *
         * @return
         */
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        /**
         * 用户帐号是否未锁定
         *
         * @return
         */
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        /**
         * 用户密码是否未过期
         *
         * @return
         */
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        /**
         * 用户是否可用
         *
         * @return
         */
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    

    上面的 User 类实现了 UserDetails 接口,该接口是实现Spring Security 认证信息的核心接口。其中 getUsername 方法为 UserDetails 接口 的方法,这个方法返回 username,也可以是其他的用户信息,例如手机号、邮箱等。getAuthorities() 方法返回的是该用户设置的权限信息,在本实例中,从数据库取出用户的所有角色信息,权限信息也可以是用户的其他信息,不一定是角色信息。另外需要读取密码,最后几个方法一般情况下都返回 true,也可以根据自己的需求进行业务判断。

  • com.example.backend_template.entity下新建Role类

    package com.example.backend_template.entity;
    
    import org.springframework.security.core.GrantedAuthority;
    
    /**
     * @ClassName Role
     * @Description
     * @Author L
     * @Date Create by 2020/6/28
     */
    public class Role implements GrantedAuthority {
        private Long id;
        private String name;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String getAuthority() {
            return name;
        }
    }
    

    Role 类实现了 GrantedAuthority 接口,并重写 getAuthority() 方法。权限点可以为任何字符串,不一定非要用角色名。
    AuthenticationManager会设置到一个GrantedAuthority列表到Authentication对象中保存,GrantedAuthority列表表示用户所具有的权限,AccessDecisionManager将从Authentication中获取用户的GrantedAuthority来鉴定用户是否具有访问权限。

  • com.example.backend_template.dao下新建UserDao接口

    package com.example.backend_template.dao;
    
    import com.example.backend_template.entity.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    /**
     * @ClassName UserDao
     * @Description
     * @Author L
     * @Date Create by 2020/6/28
     */
    @Mapper
    public interface UserDao {
        /**
         * 通过用户名加载用户
         *
         * @param userName
         * @return
         */
        User findByUsername(@Param("userName") String userName);
    }
    
  • resources/mapper下新建UserDao.xml文件

    <?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.example.backend_template.dao.UserDao">
        <select id="findByUsername" parameterType="string" resultType="com.example.backend_template.entity.User">
        SELECT * FROM user u WHERE u.username = #{userName}
        </select>
    
    </mapper>
    
  • com.example.backend_template.dao下新建RoleDao接口

    package com.example.backend_template.dao;
    
    import com.example.backend_template.entity.Role;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.data.repository.query.Param;
    
    import java.util.List;
    
    /**
     * @ClassName RoleDao
     * @Description
     * @Author L
     * @Date Create by 2020/6/28
     */
    @Mapper
    public interface RoleDao {
        /**
         * 通过用户ID查找用户角色
         *
         * @param userId
         * @return
         */
        List<Role> findByUserId(@Param("userId") Long userId);
    }
    
  • resources/mapper下新建RoleDao.xml文件

    <?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.example.backend_template.dao.RoleDao">
        <select id="findByUserId" parameterType="Long" resultType="com.example.backend_template.entity.Role">
        SELECT * FROM role r WHERE r.id IN (SELECT ur.role_id FROM user_role ur WHERE ur.user_id = #{userId} )
        </select>
    
    </mapper>
    
  • com.example.backend_template.dao下新建PermissionDao接口

    package com.example.backend_template.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * @ClassName PermissionDao
     * @Description 
     * @Author L
     * @Date Create by 2020/6/29
     */
    @Mapper
    public interface PermissionDao {
    
        List<Map<String, String>> findRoleAndPermissions();
    }
    
  • resources/mappers下新建PermissionDao.xml文件

    <?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.example.backend_template.dao.PermissionDao">
        <select id="findRoleAndPermissions" resultType="java.util.HashMap">
        SELECT R.name,P.url FROM role AS R LEFT JOIN role_permission RP ON R.id=RP.role_id LEFT JOIN permission AS P ON RP.permission_id=P.id
        </select>
        
    </mapper>
    
  • 并在BackendTemplateApplication启动类中添加@MapperScan("com.example.backend_template.dao")注解,如之前未删除则不再添加

  • com.example.backend_template.security下新建UserDetailsServiceImpl类

    package com.example.backend_template.security;
    
    import com.example.backend_template.dao.RoleDao;
    import com.example.backend_template.dao.UserDao;
    import com.example.backend_template.entity.Role;
    import com.example.backend_template.entity.User;
    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;
    
    /**
     * @ClassName UserDetailsService
     * @Description 
     * @Author L
     * @Date Create by 2020/6/29
     */
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserDao userDao;
        @Autowired
        private RoleDao roleDao;
    
        @Override
        public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
            //查数据库,查找到用户名对应的所有角色,并注入user中
            User user = userDao.findByUsername(userName);
            if (user != null) {
                List<Role> roles = roleDao.findByUserId(user.getId());
                user.setAuthorities(roles);
            }
            return user;
        }
    }
    
  • com.example.backend_template.security下新建InvocationSecurityMetadataSourceServiceImpl类

    package com.example.backend_template.security;
    
    import com.example.backend_template.dao.PermissionDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.access.SecurityConfig;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.*;
    
    /**
     * @ClassName InvocationSecurityMetadataSourceServiceImpl
     * @Description 
     * @Author L
     * @Date Create by 2020/6/29
     */
    @Component
    public class InvocationSecurityMetadataSourceServiceImpl implements FilterInvocationSecurityMetadataSource {
    
        @Autowired
        private PermissionDao permissionDao;
    
        /**
         * 每一个资源所需要的角色 Collection<ConfigAttribute>决策器会用到
         */
        private static HashMap<String, Collection<ConfigAttribute>> map = null;
    
        /**
         * 返回请求的资源需要的角色
         *
         * @param o
         * @return
         * @throws IllegalArgumentException
         */
        @Override
        public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
            //object 中包含用户请求的request 信息
            HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
            for (Iterator<String> it = map.keySet().iterator(); it.hasNext(); ) {
                String url = it.next();
                if (new AntPathRequestMatcher(url).matches(request)) {
                    return map.get(url);
                }
            }
            return null;
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            //初始化 所有资源 对应的角色
            loadResourceDefine();
            return null;
        }
    
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    
        /**
         * 初始化 所有资源 对应的角色
         */
        public void loadResourceDefine() {
            map = new HashMap<>(16);
            //查出结果为角色和对应URL的集合
            List<Map<String, String>> roleAndPermissions = permissionDao.findRoleAndPermissions();
    
            //某个资源可以被哪些角色访问
            for (Map<String, String> roleAndPermission : roleAndPermissions) {
    
                String roleName = roleAndPermission.get("name");
                String url = roleAndPermission.get("url");
                ConfigAttribute role = new SecurityConfig(roleName);
    
                if (map.containsKey(url)) {
                    map.get(url).add(role);
                } else {
                    List<ConfigAttribute> list = new ArrayList<>();
                    list.add(role);
                    map.put(url, list);
                }
            }
        }
    }
    

    InvocationSecurityMetadataSourceServiceImpl 类实现了 FilterInvocationSecurityMetadataSource,FilterInvocationSecurityMetadataSource 的作用是用来储存请求与权限的对应关系。

    FilterInvocationSecurityMetadataSource接口有3个方法:

    boolean supports(Class<?> clazz):指示该类是否能够为指定的方法调用或Web请求提供ConfigAttributes。
    Collection getAllConfigAttributes():Spring容器启动时自动调用, 一般把所有请求与权限的对应关系也要在这个方法里初始化, 保存在一个属性变量里。
    Collection getAttributes(Object object):当接收到一个http请求时, filterSecurityInterceptor会调用的方法. 参数object是一个包含url信息的HttpServletRequest实例. 这个方法要返回请求该url所需要的所有权限集合

  • com.example.backend_template.security下新建AccessDecisionManagerImpl类

    package com.example.backend_template.security;
    
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.stereotype.Component;
    
    import java.util.Collection;
    import java.util.Iterator;
    
    /**
     * @ClassName AccessDecisionManagerImpl
     * @Description 
     * @Author L
     * @Date Create by 2020/6/30
     */
    @Component
    public class AccessDecisionManagerImpl implements AccessDecisionManager {
    
        /**
         * 通过传递的参数来决定用户是否有访问对应受保护对象的权限
         *
         * @param authentication   包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时UserDetailsService中设置的authorities。
         * @param object           就是FilterInvocation对象,可以得到request等web资源
         * @param configAttributes configAttributes是本次访问需要的权限
         */
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            if (configAttributes == null || configAttributes.size() <= 0) {
                return;
            } else {
                String needRole;
                for (Iterator<ConfigAttribute> iterator = configAttributes.iterator(); iterator.hasNext(); ) {
                    needRole = iterator.next().getAttribute();
                    for (GrantedAuthority ga : authentication.getAuthorities()) {
                        if (needRole.trim().equals(ga.getAuthority().trim())) {
                            return;
                        }
                    }
                }
            }
            throw new AccessDeniedException("当前没有访问权限!");
        }
    
        /**
         * 表示此AccessDecisionManager是否能够处理传递的ConfigAttribute呈现的授权请求
         *
         * @param configAttribute
         * @return
         */
        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return true;
        }
    
        /**
         * 表示当前AccessDecisionManager实现是否能够为指定的安全对象(方法调用或Web请求)提供访问控制决策
         *
         * @param aClass
         * @return
         */
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    }
    

    AccessDecisionManagerImpl 类实现了AccessDecisionManager接口,AccessDecisionManager是由AbstractSecurityInterceptor调用的,它负责鉴定用户是否有访问对应资源(方法或URL)的权限。

  • com.example.backend_template.security下新建FilterSecurityInterceptorImpl类

    package com.example.backend_template.security;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.SecurityMetadataSource;
    import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
    import org.springframework.security.access.intercept.InterceptorStatusToken;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    /**
     * @ClassName FilterSecurityInterceptorImpl
     * @Description
     * @Author L
     * @Date Create by 2020/6/30
     */
    @Component
    public class FilterSecurityInterceptorImpl extends AbstractSecurityInterceptor implements Filter {
    
        @Autowired
        private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
        @Autowired
        public void setAccessDecisionManagerImpl(AccessDecisionManagerImpl accessDecisionManagerImpl) {
            super.setAccessDecisionManager(accessDecisionManagerImpl);
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
            invoke(fi);
        }
    
        public void invoke(FilterInvocation fi) throws IOException, ServletException {
            InterceptorStatusToken token = super.beforeInvocation(fi);
            try {
                //执行下一个拦截器
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.afterInvocation(token, null);
            }
        }
    
        @Override
        public Class<?> getSecureObjectClass() {
            return FilterInvocation.class;
        }
    
        @Override
        public SecurityMetadataSource obtainSecurityMetadataSource() {
            return this.securityMetadataSource;
        }
    }
    

    每种受支持的安全对象类型(方法调用或Web请求)都有自己的拦截器类,它是AbstractSecurityInterceptor的子类,AbstractSecurityInterceptor 是一个实现了对受保护对象的访问进行拦截的抽象类。
    AbstractSecurityInterceptor中的方法说明:
    beforeInvocation()方法实现了对访问受保护对象的权限校验,内部用到了AccessDecisionManager和AuthenticationManager;
    finallyInvocation()方法用于实现受保护对象请求完毕后的一些清理工作,主要是如果在beforeInvocation()中改变了SecurityContext,则在finallyInvocation()中需要将其恢复为原来的SecurityContext,该方法的调用应当包含在子类请求受保护资源时的finally语句块中。
    afterInvocation()方法实现了对返回结果的处理,在注入了AfterInvocationManager的情况下默认会调用其decide()方法。
    了解了AbstractSecurityInterceptor,就应该明白了,我们自定义FilterSecurityInterceptorImpl就是想使用我们之前自定义的 AccessDecisionManager 和 securityMetadataSource。

  • com.example.backend_template.security下新建SecurityConfig类

    package com.example.backend_template.security;
    
    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.password.PasswordEncoder;
    import org.springframework.util.DigestUtils;
    
    /**
     * @ClassName SecurityConfig
     * @Description 
     * @Author L
     * @Date Create by 2020/6/30
     */
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private UserDetailsServiceImpl userService;
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            //校验用户
            auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
                //对密码进行加密
                @Override
                public String encode(CharSequence charSequence) {
                    return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                }
                //对密码进行判断匹配
                @Override
                public boolean matches(CharSequence charSequence, String s) {
                    String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                    boolean res = s.equals(encode);
                    return res;
                }
            });
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/", "/index", "/login", "/login-error", "/401").permitAll()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().loginPage("/login").failureUrl("/login-error")
                    .and()
                    .exceptionHandling().accessDeniedPage("/401");
            http.logout().logoutSuccessUrl("/");
        }
    }
    

    @EnableWebSecurity注解以及WebSecurityConfigurerAdapter一起配合提供基于web的security。自定义类继承了WebSecurityConfigurerAdapter来重写了一些方法来指定一些特定的Web安全设置

测试
  • com.example.backend_template.controller下新建SecurityController类
    package com.example.backend_template.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @ClassName SecurityController
     * @Description 
     * @Author L
     * @Date Create by 2020/6/30
     */
    @Controller
    public class SecurityController {
        @RequestMapping("/")
        public String root() {
            return "redirect:/index";
        }
        @RequestMapping("/index")
        public String index() {
            return "index";
        }
        @RequestMapping("/login")
        public String login() {
            return "login";
        }
        @RequestMapping("/login-error")
        public String loginError(Model model) {
            model.addAttribute( "loginError"  , true);
            return "login";
        }
        @GetMapping("/401")
        public String accessDenied() {
            return "401";
        }
        @GetMapping("/user/common")
        public String common() {
            return "user/common";
        }
        @GetMapping("/user/admin")
        public String admin() {
            return "user/admin";
        }
    }
    
  • resources/templates下新建401.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>401 page</title>
    </head>
    <body>
        <div>
            <div>
                <h2>权限不够</h2>
                <p>拒绝访问!</p>
            </div>
        </div>
    </body>
    </html>
    
  • resources/templates下新建index.html
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        <h2>page list</h2>
        <a href="/user/common">common page</a>
        <br/>
        <a href="/user/admin">admin page</a>
        <br/>
        <form th:action="@{/logout}" method="post">
            <input type="submit" class="btn btn-primary" value="注销"/>
        </form>
    </body>
    </html>
    
  • resources/templates下新建login.html
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
        <h1>Login page</h1>
        <p th:if="${loginError}" class="error">用户名或密码错误</p>
        <form th:action="@{/login}" method="post">
            <label for="username">用户名</label>:
            <input type="text" id="username" name="username" autofocus="autofocus" />
            <br/>
            <label for="password">密 码</label>:
            <input type="password" id="password" name="password" />
            <br/>
            <input type="submit" value="登录" />
        </form>
        <p><a href="/index" th:href="@{/index}"></a></p>
    </body>
    </html>
    
  • 首先在resources/templates下新建user文件夹,然后在resources/templates/user下新建admin.html
    <!DOCTYPE html>
    <head>
        <meta charset="UTF-8">
        <title>admin page</title>
    </head>
    <body>
        success admin page!!!
    </body>
    </html>
    
  • resources/templates/user下新建common.html
    <!DOCTYPE html>
    <head>
        <meta charset="UTF-8">
        <title>common page</title>
    </head>
    <body>
        success common page!!!
    </body>
    </html>
    
  • 启动项目,并用浏览器访问 http://localhost:8080/index 并选择 common page
    在这里插入图片描述
  • 输入用户名:”user“,密码:”123456“,出现以下界面,说明登陆成功
    在这里插入图片描述
  • 接着访问 http://localhost:8080/user/admin ,会出现以下界面,因为user用户没有权力访问/user/admin界面,必须用admin帐号才能访问
    在这里插入图片描述
    如成功出现以上所有结果,则Spring Security 整合成功
项目地址

项目介绍:从零搭建 Spring Boot 后端项目
代码地址:https://github.com/xiaoxiamo/backend-template

下一篇

五、整合Swagger2

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夏陌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值