从业两年一直对登陆框架没有系统的学习今天接着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查看在线实例