springboot+Security+thymeleaf

从业两年一直对登陆框架没有系统的学习今天接着https://blog.csdn.net/code__code/article/details/53885510基础上做一个总结,花了大约半天时间搭建了这套传统的spring security的登陆验证,所有代码亲测完全可以运行

先写一下技术栈

1、spring boot2.0

2、spring data-jpa

3、thymeleaf

4、Security5

5、springboot-web

6、mysql-connector-java

7、jdk1.8

这里附上gradle的包引用

buildscript {
    ext {
        springBootVersion = '2.0.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.jiakong.framework'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile("org.springframework.boot:spring-boot-starter-web")
    runtime('mysql:mysql-connector-java')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.springframework.security:spring-security-test')
}

在贴出项目结构


下面会将所有用到的类全部列出直接粘贴复制可运行:locahost:8085

实体类

package com.jiakong.framework.springbootsecurity.user.entity;

import javax.persistence.*;

/**
 * SysResource
 *
 * @author admin
 * @date 2018-05-22-16
 */
@Entity
@Table(name="s_resource")
public class SysResource {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id",length=10)
    private int id;

    /**
     * url
     */
    @Column(name="resourceString",length=1000)
    private String resourceString;

    /**
     * 资源ID
     */
    @Column(name="resourceId",length=50)
    private String resourceId;

    /**
     * 备注
     */
    @Column(name="remark",length=200)
    private String remark;

    /**
     * 资源名称
     */
    @Column(name="resourceName",length=400)
    private String resourceName;
    /**
     * 资源所对应的方法名
     */
    @Column(name="methodName",length=400)
    private String methodName;

    /**
     * 资源所对应的包路径
     */
    @Column(name="methodPath",length=1000)
    private String methodPath;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getResourceString() {
        return resourceString;
    }

    public void setResourceString(String resourceString) {
        this.resourceString = resourceString;
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public String getResourceName() {
        return resourceName;
    }

    public void setResourceName(String resourceName) {
        this.resourceName = resourceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getMethodPath() {
        return methodPath;
    }

    public void setMethodPath(String methodPath) {
        this.methodPath = methodPath;
    }
}
package com.jiakong.framework.springbootsecurity.user.entity;

import javax.persistence.*;
import java.util.Date;

/**
 * SysResourceRole
 *
 * @author admin
 * @date 2018-05-22-16
 */
@Entity
@Table(name="s_resource_role")
public class SysResourceRole {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id",length=10)
    private int id;

    /**
     * 角色ID
     */
    @Column(name="roleId",length=50)
    private String roleId;

    /**
     * 资源ID
     */
    @Column(name="resourceId",length=50)
    private String resourceId;

    /**
     * 更新时间
     */
    @Column(name="updateTime")
    private Date updateTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getRoleId() {
        return roleId;
    }

    public void setRoleId(String roleId) {
        this.roleId = roleId;
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}

package com.jiakong.framework.springbootsecurity.user.entity;

import javax.persistence.*;

/**
 * SysRole
 *角色表
 * @author admin
 * @date 2018-05-22-16
 */
@Entity
@Table(name="s_role")
public class SysRole {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column (name="id",length=10)
    private int id;

    /**
     * 角色对应的用户实体
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "uid", nullable = false)
    private SysUser SUser;

    /**
     * 角色名称
     */
    @Column(name="name",length=100)
    private String name;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public SysUser getSUser() {
        return SUser;
    }
    public void setSUser(SysUser sUser) {
        SUser = sUser;
    }
}

package com.jiakong.framework.springbootsecurity.user.entity;

import javax.persistence.*;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * SysUser
 *
 * @author admin
 * @date 2018-05-22-16
 */
@Entity
@Table(name = "s_user")
public class SysUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;
    @Column(name = "name", length = 120)
    private String name;
    @Column(name = "email", length = 50)
    private String email;
    @Column(name = "password", length = 120)
    private String password;
    @Temporal(TemporalType.DATE)
    @Column(name = "dob", length = 10)
    private Date dob;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "SUser")

    /**
     *  所对应的角色集合
     */
    private Set<SysRole> SysRoles = new HashSet<SysRole>(0);

    public SysUser() {
    }

    public SysUser(String name, String email, String password, Date dob, Set<SysRole> SysRoles) {
        this.name = name;
        this.email = email;
        this.password = password;
        this.dob = dob;
        this.SysRoles = SysRoles;
    }


    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }


    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    public Date getDob() {
        return this.dob;
    }

    public void setDob(Date dob) {
        this.dob = dob;
    }

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "SUser")
    public Set<SysRole> getSysRoles() {
        return this.SysRoles;
    }

    public void setSRoles(Set<SysRole> SysRoles) {
        this.SysRoles = SysRoles;
    }
}

下面是Repostitory

package com.jiakong.framework.springbootsecurity.user.repository;

import com.jiakong.framework.springbootsecurity.user.entity.SysResource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/**
 * SysResourceRpository
 *
 * @author yangpeng
 * @date 2018-05-23-09
 */
public interface SysResourceRpository extends JpaRepository<SysResource, Long> {

    @Query("select s from SysResource s where s.resourceName = ?1")
    List<SysResource> findByName(String s);
}
package com.jiakong.framework.springbootsecurity.user.repository;

import com.jiakong.framework.springbootsecurity.user.entity.SysRole;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * SysRoleRrpository
 *
 * @author yangpeng
 * @date 2018-05-23-09
 */
public interface SysRoleRrpository extends JpaRepository<SysRole,Long> {
}

package com.jiakong.framework.springbootsecurity.user.repository;

import com.jiakong.framework.springbootsecurity.user.entity.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * UserRepository
 *
 * @author yangpeng
 * @date 2018-05-22-18
 */
public interface UserRepository extends JpaRepository<SysUser, Long> {
    /**
     * 根据用户名查询
     *
     * @param name
     * @return
     */
    SysUser findByName(String name);
}

service

package com.jiakong.framework.springbootsecurity.user.service;

import com.jiakong.framework.springbootsecurity.user.entity.SysUser;
import com.jiakong.framework.springbootsecurity.user.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * UserService
 *
 * @author yangpeng
 * @date 2018-05-22-17
 */

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public SysUser findByName(String username) {
        SysUser user = userRepository.findByName(username);
        return user;
    }

    public void update(SysUser su) {
        userRepository.save(su);
    }
}


下面是最重要的部分

com.jiakong.framework.springbootsecurity.config.Appctx
com.jiakong.framework.springbootsecurity.config.CustomAccessDecisionManager
com.jiakong.framework.springbootsecurity.config.CustomInvocationSecurityMetadataSourceService
com.jiakong.framework.springbootsecurity.config.CustomUserDetailsService
com.jiakong.framework.springbootsecurity.config.LoginSuccessHandler
com.jiakong.framework.springbootsecurity.config.MvcConfig
com.jiakong.framework.springbootsecurity.config.MySecurityFilter
com.jiakong.framework.springbootsecurity.config.SecurityUser
com.jiakong.framework.springbootsecurity.config.ServletInitializer

com.jiakong.framework.springbootsecurity.config.WebSecurityConfig

上面这这几个类是项目的全部配置文件

package com.jiakong.framework.springbootsecurity.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
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.builders.WebSecurity;
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.access.intercept.FilterSecurityInterceptor;

/**
 * WebSecurityConfig
 *
 * @author admin
 * @date 2018-05-22-16
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    // TODO: 2018/5/23 添加权限控制所添加的

    @Autowired
    private MySecurityFilter mySecurityFilter;

    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 在正确的位置添加我们自定义的过滤器
     * http://localhost:8080/login 输入正确的用户名密码 并且选中remember-me 则登陆成功,转到 index页面
     * 再次访问index页面无需登录直接访问
     * 访问http://localhost:8080/home 不拦截,直接访问,
     * 访问http://localhost:8080/hello 需要登录验证后,且具备 “ADMIN”权限hasAuthority("ADMIN")才可以访问
     * .antMatchers("/home").permitAll()//访问:/home 无需登录认证权限
     * .anyRequest().authenticated() //其他所有资源都需要认证,登陆后访问
     * .antMatchers("/hello").hasAuthority("ADMIN") //登陆后之后拥有“ADMIN”权限才可以访问/hello方法,否则系统会出现“403”权限不足的提示
     * LoginSuccessHandler .successHandler(loginSuccessHandler()) //登录成功后可使用loginSuccessHandler()存储用户信息,可选。
     * .logoutSuccessUrl("/home") //退出登录后的默认网址是”/home”
     * .loginPage("/login")//指定登录页是”/login”
     * .rememberMe()//登录后记住用户,下次自动登录,数据库中必须存在名为persistent_logins的表
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(mySecurityFilter, FilterSecurityInterceptor.class)
                .authorizeRequests()
                .antMatchers("/home").permitAll()
                .anyRequest().authenticated()
                .antMatchers("/hello").hasAuthority("ADMIN")
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .successHandler(loginSuccessHandler())
                .and()
                .logout()
                .logoutSuccessUrl("/home")
                .permitAll()
                .invalidateHttpSession(true)
                .and()
                .rememberMe()
                .tokenValiditySeconds(1209600);
    }

    /**
     * 指定密码加密所使用的加密器为passwordEncoder()
     * 需要将密码加密后写入数据库
     * <p>
     * eraseCredentials 不删除凭据,以便记住用户
     *
     * @param builder
     * @throws Exception
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
        builder.userDetailsService(customUserDetailsService).passwordEncoder(PasswordEncoder());
        builder.eraseCredentials(false);
    }

    @Bean
    public BCryptPasswordEncoder PasswordEncoder() {
        return new BCryptPasswordEncoder(4);
    }

    @Bean
    public LoginSuccessHandler loginSuccessHandler() {
        return new LoginSuccessHandler();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

package com.jiakong.framework.springbootsecurity.config;

import com.jiakong.framework.springbootsecurity.SpringbootSecurityApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;

import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

/**
 * ServletInitializer
 *
 * @author admin
 * @date 2018-05-23-10
 */
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(SpringbootSecurityApplication.class);
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        FilterRegistration.Dynamic openEntityManagerInViewFilter = servletContext.addFilter("openEntityManagerInViewFilter", OpenEntityManagerInViewFilter.class);
        openEntityManagerInViewFilter.setInitParameter("entityManagerFactoryBeanName", "entityManagerFactory");
        openEntityManagerInViewFilter.addMappingForServletNames(null, false, "/*");
        super.onStartup(servletContext);
    }
}
package com.jiakong.framework.springbootsecurity.config;

import com.jiakong.framework.springbootsecurity.user.entity.SysRole;
import com.jiakong.framework.springbootsecurity.user.entity.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

/**
 * SecurityUser
 *
 * @author admin
 * @date 2018-05-22-17
 */
public class SecurityUser extends SysUser implements UserDetails {
    private static final Long serialVersionUID = 1L;

    /**
     * 将传入的用户保存到用户
     *
     * @param sysUser
     */
    public SecurityUser(SysUser sysUser) {
        if (sysUser != null) {
            this.setId(sysUser.getId());
            this.setDob(sysUser.getDob());
            this.setEmail(sysUser.getEmail());
            this.setPassword(sysUser.getPassword());
            this.setSRoles(sysUser.getSysRoles());
            this.setName(sysUser.getName());
        }
    }

    /**
     * Returns the authorities granted to the user. Cannot return <code>null</code>.
     *
     * @return the authorities, sorted by natural key (never <code>null</code>)
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        Set<SysRole> userRoles = this.getSysRoles();
        if (userRoles != null) {
            for (SysRole role : userRoles) {
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getName());
                authorities.add(authority);
            }
        }
        return authorities;
    }

    /**
     * Returns the username used to authenticate the user. Cannot return <code>null</code>.
     *
     * @return the username (never <code>null</code>)
     */
    @Override
    public String getUsername() {
        return super.getName();
    }

    /**
     * Indicates whether the user's account has expired. An expired account cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user's account is valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * Indicates whether the user is locked or unlocked. A locked user cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * Indicates whether the user's credentials (password) has expired. Expired
     * credentials prevent authentication.
     *
     * @return <code>true</code> if the user's credentials are valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * Indicates whether the user is enabled or disabled. A disabled user cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

package com.jiakong.framework.springbootsecurity.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.*;
import java.io.IOException;

/**
 * MyFilterSecurityInterceptor
 * 该过滤器的主要作用就是通过spring著名的IoC生成securityMetadataSource。
 * securityMetadataSource相当于本包中自定义的MyInvocationSecurityMetadataSourceService。
 * 该MyInvocationSecurityMetadataSourceService的作用提从数据库提取权限和资源,装配到HashMap中,
 * 供Spring Security使用,用于权限校验。
 *
 * @author admin
 * @date 2018-05-23-10
 */
@Component
public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter {

    @Autowired
    private CustomInvocationSecurityMetadataSourceService customInvocationSecurityMetadataSourceService;
    @Autowired
    private CustomAccessDecisionManager customAccessDecisionManager;
    @Autowired
    public AuthenticationManager authenticationManager;


    @PostConstruct
    public void init() {
        super.setAuthenticationManager(authenticationManager);
        super.setAccessDecisionManager(customAccessDecisionManager);
    }

    /**
     * Called by the web container to indicate to a filter that it is being
     * placed into service. The servlet container calls the init method exactly
     * once after instantiating the filter. The init method must complete
     * successfully before the filter is asked to do any filtering work.
     * <p>
     * The web container cannot place the filter into service if the init method
     * either:
     * <ul>
     * <li>Throws a ServletException</li>
     * <li>Does not return within a time period defined by the web
     * container</li>
     * </ul>
     *
     * @param filterConfig The configuration information associated with the
     *                     filter instance being initialised
     * @throws ServletException if the initialisation fails
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("------------" + getClass().getCanonicalName() + "-----------");
    }

    /**
     * The <code>doFilter</code> method of the Filter is called by the container
     * each time a request/response pair is passed through the chain due to a
     * client request for a resource at the end of the chain. The FilterChain
     * passed in to this method allows the Filter to pass on the request and
     * response to the next entity in the chain.
     * <p>
     * A typical implementation of this method would follow the following
     * pattern:- <br>
     * 1. Examine the request<br>
     * 2. Optionally wrap the request object with a custom implementation to
     * filter content or headers for input filtering <br>
     * 3. Optionally wrap the response object with a custom implementation to
     * filter content or headers for output filtering <br>
     * 4. a) <strong>Either</strong> invoke the next entity in the chain using
     * the FilterChain object (<code>chain.doFilter()</code>), <br>
     * 4. b) <strong>or</strong> not pass on the request/response pair to the
     * next entity in the filter chain to block the request processing<br>
     * 5. Directly set headers on the response after invocation of the next
     * entity in the filter chain.
     *
     * @param request  The request to process
     * @param response The response associated with the request
     * @param chain    Provides access to the next filter in the chain for this
     *                 filter to pass the request and response to for further
     *                 processing
     * @throws IOException      if an I/O error occurs during this filter's
     *                          processing of the request
     * @throws ServletException if the processing fails for any other reason
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
        invoke(filterInvocation);

    }

    private void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
        InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
        try {
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    /**
     * Called by the web container to indicate to a filter that it is being
     * taken out of service. This method is only called once all threads within
     * the filter's doFilter method have exited or after a timeout period has
     * passed. After the web container calls this method, it will not call the
     * doFilter method again on this instance of the filter. <br>
     * <br>
     * <p>
     * This method gives the filter an opportunity to clean up any resources
     * that are being held (for example, memory, file handles, threads) and make
     * sure that any persistent state is synchronized with the filter's current
     * state in memory.
     */
    @Override
    public void destroy() {
        logger.info("-----end-------" + getClass().getCanonicalName() + "------end-----");
    }

    /**
     * Indicates the type of secure objects the subclass will be presenting to the
     * abstract parent for processing. This is used to ensure collaborators wired to the
     * {@code AbstractSecurityInterceptor} all support the indicated secure object class.
     *
     * @return the type of secure object the subclass provides services for
     */
    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.customInvocationSecurityMetadataSourceService;
    }
}

package com.jiakong.framework.springbootsecurity.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * MvcConfig
 *
 * @author admin
 * @date 2018-05-22-17
 */
@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }
}

package com.jiakong.framework.springbootsecurity.config;

import com.jiakong.framework.springbootsecurity.user.entity.SysUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * LoginSuccessHandler
 *
 * @author admin
 * @date 2018-05-22-17
 */
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private static Logger logger = LoggerFactory.getLogger(LoginSuccessHandler.class);

    /**
     * //获得授权后可得到用户信息   可使用SUserService进行数据库操作
     * //输出登录提示信息
     *
     * @param request
     * @param response
     * @param authentication
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        SysUser userDetails = (SysUser) authentication.getPrincipal();
        logger.info("管理员" + userDetails.getName() + "登录");
        logger.info("IP" + getIpAddress(request));
        super.onAuthenticationSuccess(request, response, authentication);
    }

    private String getIpAddress(HttpServletRequest request) {
        String unknown = "unknown";
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

package com.jiakong.framework.springbootsecurity.config;

import com.jiakong.framework.springbootsecurity.user.entity.SysUser;
import com.jiakong.framework.springbootsecurity.user.service.UserService;
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.Component;

/**
 * CustomUserDetailsService
 *
 * @author admin
 * @date 2018-05-22-16
 */
@Component("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userService.findByName(username);
        if (user == null){
            throw new UsernameNotFoundException("userName"+ username+"not found");
        }
        SecurityUser securityUser = new SecurityUser(user);
        return securityUser;
    }
}

package com.jiakong.framework.springbootsecurity.config;

import com.jiakong.framework.springbootsecurity.user.entity.SysResource;
import com.jiakong.framework.springbootsecurity.user.entity.SysRole;
import com.jiakong.framework.springbootsecurity.user.repository.SysResourceRpository;
import com.jiakong.framework.springbootsecurity.user.repository.SysRoleRrpository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * CustomInvocationSecurityMetadataSourceService
 *
 * @author admin
 * @date 2018-05-23-09
 */
@Service
public class CustomInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
    private static Logger logger = LoggerFactory.getLogger(CustomInvocationSecurityMetadataSourceService.class);

    @Autowired
    private SysResourceRpository resourceRpository;
    @Autowired
    private SysRoleRrpository roleRrpository;

    private static Map<String, Collection<ConfigAttribute>> resourMap = null;

    /**
     * 被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
     * 一定要加上@PostConstruct注解
     * 一定要加上@PostConstruct注解
     * 一定要加上@PostConstruct注解
     * resourMap 应当是资源为key, 权限为value。 资源通常为url, 权限就是那些以ROLE_为前缀的角色。 一个资源可以由多个权限来访问。
     * String url = s1 判断资源文件和权限的对应关系,如果已经存在相关的资源url,则要通过该url为key提取出权限集合,将权限增加到权限集合中。
     */
    @PostConstruct
    private void loadResourceDefine() {
        List<SysRole> roleList = roleRrpository.findAll();
        List<String> query = new LinkedList<>();
        if (roleList != null && roleList.size() > 0) {
            roleList.forEach(sysRole -> {
                query.add(sysRole.getName());
            });
        }
        resourMap = new HashMap<String, Collection<ConfigAttribute>>();
        query.forEach(s -> {
            ConfigAttribute configAttribute = new SecurityConfig(s);
            List<String> query1 = new LinkedList<>();
            List<SysResource> list = resourceRpository.findByName(s);
            if (list != null && list.size() > 0) {
                list.forEach(sysResource -> {
                    query1.add(sysResource.getResourceString());
                });
            }
            query1.forEach(s1 -> {
                String url = s1;
                if (resourMap.containsKey(url)) {
                    Collection<ConfigAttribute> value = resourMap.get(url);
                    value.add(configAttribute);
                    resourMap.put(url, value);
                } else {
                    Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
                    atts.add(configAttribute);
                    resourMap.put(url, atts);
                }
            });
        });
    }

    /**
     * Accesses the {@code ConfigAttribute}s that apply to a given secure object.
     *
     * @param object the object being secured
     * @return the attributes that apply to the passed in secured object. Should return an
     * empty collection if there are no applicable attributes.
     * @throws IllegalArgumentException if the passed object is not of a type supported by
     *                                  the <code>SecurityMetadataSource</code> implementation
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation filterInvocation = (FilterInvocation) object;
        if (resourMap == null){
            loadResourceDefine();
        }
        Iterator<String> iterator = resourMap.keySet().iterator();
        while (iterator.hasNext()){
            String resURL = iterator.next();
            RequestMatcher requestMatcher = new AntPathRequestMatcher(resURL);
            if (requestMatcher.matches(filterInvocation.getHttpRequest())){
                return resourMap.get(resURL);
            }
        }
        return null;
    }

    /**
     * If available, returns all of the {@code ConfigAttribute}s defined by the
     * implementing class.
     * <p>
     * This is used by the {@link } to perform startup time
     * validation of each {@code ConfigAttribute} configured against it.
     *
     * @return the {@code ConfigAttribute}s or {@code null} if unsupported
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * Indicates whether the {@code SecurityMetadataSource} implementation is able to
     * provide {@code ConfigAttribute}s for the indicated secure object type.
     *
     * @param clazz the class that is being queried
     * @return true if the implementation can process the indicated class
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

package com.jiakong.framework.springbootsecurity.config;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Iterator;

/**
 * CustomAccessDecisionManager
 * AccessdecisionManager在Spring security中是很重要的。
 * 在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。
 * 这就是赋予给主体的权限。 GrantedAuthority对象通过AuthenticationManager
 * 保存到 Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。
 * Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。
 * 一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。
 * 这个 AccessDecisionManager 被AbstractSecurityInterceptor调用,
 * 它用来作最终访问控制的决定。 这个AccessDecisionManager接口包含三个方法:
 * void decide(Authentication authentication, Object secureObject,
 * List<ConfigAttributeDefinition> config) throws AccessDeniedException;
 * boolean supports(ConfigAttribute attribute);
 * boolean supports(Class clazz);
 * 从第一个方法可以看出来,AccessDecisionManager使用方法参数传递所有信息,这好像在认证评估时进行决定。
 * 特别是,在真实的安全方法期望调用的时候,传递安全Object启用那些参数。
 * 比如,让我们假设安全对象是一个MethodInvocation。
 * 很容易为任何Customer参数查询MethodInvocation,
 * 然后在AccessDecisionManager里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。
 * 如果访问被拒绝,实现将抛出一个AccessDeniedException异常。
 * 这个 supports(ConfigAttribute) 方法在启动的时候被
 * AbstractSecurityInterceptor调用,来决定AccessDecisionManager
 * 是否可以执行传递ConfigAttribute。
 * supports(Class)方法被安全拦截器实现调用,
 * 包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。
 *
 * @author admin
 * @date 2018-05-23-10
 */
@Service
public class CustomAccessDecisionManager implements AccessDecisionManager {
    /**
     * Resolves an access control decision for the passed parameters.
     *
     * @param authentication   the caller invoking the method (not null)
     * @param object           the secured object being called
     * @param configAttributes the configuration attributes associated with the secured
     *                         object being invoked
     * @throws AccessDeniedException               if access is denied as the authentication does not
     *                                             hold a required authority or ACL privilege
     * @throws InsufficientAuthenticationException if access is denied as the
     *                                             authentication does not provide a sufficient level of trust
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        if (configAttributes == null) {
            return;
        }
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute configAttribute = iterator.next();
            String needRole = ((SecurityConfig) configAttribute).getAttribute();
            authentication.getAuthorities().forEach(grantedAuthority -> {
                if (needRole.trim().equals(grantedAuthority.getAuthority().trim())) {
                    return;
                }
            });
        }
        throw new AccessDeniedException("权限不足!");
    }

    /**
     * Indicates whether this <code>AccessDecisionManager</code> is able to process
     * authorization requests presented with the passed <code>ConfigAttribute</code>.
     * <p>
     * This allows the <code>AbstractSecurityInterceptor</code> to check every
     * configuration attribute can be consumed by the configured
     * <code>AccessDecisionManager</code> and/or <code>RunAsManager</code> and/or
     * <code>AfterInvocationManager</code>.
     * </p>
     *
     * @param attribute a configuration attribute that has been configured against the
     *                  <code>AbstractSecurityInterceptor</code>
     * @return true if this <code>AccessDecisionManager</code> can support the passed
     * configuration attribute
     */
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    /**
     * Indicates whether the <code>AccessDecisionManager</code> implementation is able to
     * provide access control decisions for the indicated secured object type.
     *
     * @param clazz the class that is being queried
     * @return <code>true</code> if the implementation can process the indicated class
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

package com.jiakong.framework.springbootsecurity.config;

import org.springframework.context.ApplicationContext;

/**
 * Appctx
 *
 * @author admin
 * @date 2018-05-22-17
 */
public class Appctx {
    public static ApplicationContext ctx = null;

    public static Object getObject(String string) {
        return ctx.getBean(string);
    }
}

上述已经将全部的配置类贴出在每个类中做了详细的讲解

下面这是启动类

package com.jiakong.framework.springbootsecurity;

import com.jiakong.framework.springbootsecurity.config.Appctx;
import com.jiakong.framework.springbootsecurity.user.entity.SysUser;
import com.jiakong.framework.springbootsecurity.user.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import javax.annotation.PostConstruct;

/**
 * 安全系统启动类
 *
 * @author admin
 */
@SpringBootApplication
public class SpringbootSecurityApplication {
    private static Logger logger = LoggerFactory.getLogger(SpringbootSecurityApplication.class);

    @PostConstruct
    public void initApplication() {
        logger.info("Running with Spring profile(s) : {}");
    }

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringbootSecurityApplication.class);
        Appctx.ctx = application.run(args);
    }

    /**
     * 该方法竟在第一次启动本项目时使用
     * 后续请注释此方法调用
     * 初始化管理员密码
     */
    public void initialUser() {
        UserService suserService = (UserService) Appctx.ctx.getBean("userService");
        SysUser su = suserService.findByName("TEST");
        BCryptPasswordEncoder bc = new BCryptPasswordEncoder(4);
        su.setPassword(bc.encode(su.getPassword()));
        logger.info("密码" + su.getPassword());
        suserService.update(su);
    }


}

页面模板

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="Sign Out"/>
</form>
</body>
</html>

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Spring Security Example</title>
</head>
<body>
<h1>welcome</h1>
<p>Clik <a th:href = "@{/hello}">here</a> to see a greeting</p>
</body>
</html>

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">
    Invalid username and password.
</div>
<div th:if="${param.logout}">
    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="Sign In"/></div>
    <input type="checkbox" name="remember-me" value="true" th:checked="checked"/>
    <p>Remember me</p>
</form>
</body>
</html>

下面贴出sql脚本

/*
Navicat MySQL Data Transfer

Source Server         : 架空科技
Source Server Version : 80011
Source Host           : jiakongkeji.cn
Source Database       : springboot-security4
Date: 2018-05-23 15:18:40
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for persistent_logins
-- ----------------------------
DROP TABLE IF EXISTS `persistent_logins`;
CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) NOT NULL,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of persistent_logins
-- ----------------------------

-- ----------------------------
-- Table structure for s_resource
-- ----------------------------
DROP TABLE IF EXISTS `s_resource`;
CREATE TABLE `s_resource` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `method_name` varchar(400) DEFAULT NULL,
  `method_path` varchar(1000) DEFAULT NULL,
  `remark` varchar(200) DEFAULT NULL,
  `resource_id` varchar(50) DEFAULT NULL,
  `resource_name` varchar(400) DEFAULT NULL,
  `resource_string` varchar(1000) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of s_resource
-- ----------------------------
INSERT INTO `s_resource` VALUES ('1', null, null, '1', '1', 'ADMIN', '/hello');
INSERT INTO `s_resource` VALUES ('2', null, null, '1', '2', 'super', '/hello1');
INSERT INTO `s_resource` VALUES ('3', null, null, '1', '3', 'user', '/hello2');

-- ----------------------------
-- Table structure for s_resource_role
-- ----------------------------
DROP TABLE IF EXISTS `s_resource_role`;
CREATE TABLE `s_resource_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `resource_id` varchar(50) DEFAULT NULL,
  `role_id` varchar(50) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of s_resource_role
-- ----------------------------
INSERT INTO `s_resource_role` VALUES ('1', '1', '2', '2018-05-23 11:33:39');
INSERT INTO `s_resource_role` VALUES ('2', '2', '1', '2018-05-23 11:33:46');
INSERT INTO `s_resource_role` VALUES ('3', '3', '3', '2018-05-23 11:33:58');

-- ----------------------------
-- Table structure for s_role
-- ----------------------------
DROP TABLE IF EXISTS `s_role`;
CREATE TABLE `s_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `uid` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FKpkoo0xfyi6rd0hs9ybqv92fjp` (`uid`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of s_role
-- ----------------------------
INSERT INTO `s_role` VALUES ('1', 'ADMIN', '1');
INSERT INTO `s_role` VALUES ('2', 'super', '2');
INSERT INTO `s_role` VALUES ('3', 'user', '3');

-- ----------------------------
-- Table structure for s_user
-- ----------------------------
DROP TABLE IF EXISTS `s_user`;
CREATE TABLE `s_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dob` date DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  `name` varchar(120) DEFAULT NULL,
  `password` varchar(120) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of s_user
-- ----------------------------
INSERT INTO `s_user` VALUES ('1', '2018-05-23', '[email protected]', 'TEST', '$2a$04$PjAZFtgkuXa8kxGkypQS/O5WK/h9Vw3N5NNXyRj3U7Z32HHhH3N.W');
INSERT INTO `s_user` VALUES ('2', '2018-05-23', '[email protected]', 'user', '$2a$04$a9qutQ1qdoBrJ9aE.Kx90el6kEsajp3T/wWo0L1bsYm4wR6IVIeR6');

用户:TEST/000000,user/123456


最后项目已上传到github:跳转获取项目源码


希望对初学者有帮助!本人也是一名94的小黑胖子一枚!后期会出springboot+shiro的权限认证关注我的github或者csdn到时候可以看到。

本人保证所有代码均为本人运行实测,还可访问

jiakongkeji.cn/sbs5查看在线实例


猜你喜欢

转载自blog.csdn.net/charles_s/article/details/80419992