springboot+security 01 - 实现权限控制
参考博客: https://blog.csdn.net/Canon_in_D_Major/article/details/79688441
仅用于spring security个人学习笔记
-
当用户登录时,前端将用户输入的用户名、密码信息传输到后台,后台用一个类对象将其封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。
-
程序负责验证这个类对象。验证方法是调用Service根据username从数据库中取用户信息到实体类的实例中,比较两者的密码,如果密码正确就成功登陆,同时把包含着用户的用户名、密码、所具有的权限等信息的类对象放到SecurityContextHolder(安全上下文容器,类似Session)中去。
-
用户访问一个资源的时候,首先判断是否是受限资源。如果是的话还要判断当前是否未登录,没有的话就跳到登录页面。
-
如果用户已经登录,访问一个受限资源的时候,程序要根据url去数据库中取出该资源所对应的所有可以访问的角色,然后拿着当前用户的所有角色一一对比,判断用户是否可以访问。
- sb_security
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.20.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.youxiu326</groupId> <artifactId>sb_security</artifactId> <version>0.0.1-SNAPSHOT</version> <name>sb_security</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version> <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.2.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
server: port: 8080 context-path: / spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://youxiu326.xin:3306/security?useUnicode=true&characterEncoding=utf8 username: youxiu326 password: zz123456.ZZ jpa: show-sql: true hibernate: ddl-auto: update thymeleaf: cache: false
package com.youxiu326.bean; import org.hibernate.annotations.GenericGenerator; import javax.persistence.*; import java.io.Serializable; import java.util.List; @Entity @Table(name="sys_user") public class User implements Serializable { private String id; private String userName; private String password; /** * 拥有角色 */ private List<Role> roles; @Id @GeneratedValue(generator = "sys_uid") @GenericGenerator(name = "sys_uid", strategy = "uuid") public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "sys_user_role", joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "role_id", referencedColumnName = "id") }) public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } }
package com.youxiu326.bean; import org.hibernate.annotations.GenericGenerator; import javax.persistence.*; import java.io.Serializable; import java.util.List; @Entity @Table(name="sys_role") public class Role implements Serializable { private String id; private String roleName; private String roleNameZh; /** * 角色拥有资源 */ private List<Resource> resources; @Id @GeneratedValue(generator = "sys_uid") @GenericGenerator(name = "sys_uid", strategy = "uuid") public String getId() { return id; } public void setId(String id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleNameZh() { return roleNameZh; } public void setRoleNameZh(String roleNameZh) { this.roleNameZh = roleNameZh; } @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "sys_role_resource", joinColumns = { @JoinColumn(name = "role_id", referencedColumnName = "id") }, inverseJoinColumns = {@JoinColumn(name = "resource_id", referencedColumnName = "id") }) public List<Resource> getResources() { return resources; } public void setResources(List<Resource> resources) { this.resources = resources; } }
package com.youxiu326.bean; import com.fasterxml.jackson.annotation.JsonIgnore; import org.hibernate.annotations.GenericGenerator; import javax.persistence.*; import java.io.Serializable; import java.util.List; @Entity @Table(name="sys_Resource") public class Resource implements Serializable { private String id; private String url; private String resName; /** * 上级权限 */ private Resource parent; /** * 下级权限 */ private List<Resource> children; @Id @GeneratedValue(generator = "sys_uid") @GenericGenerator(name = "sys_uid", strategy = "uuid") public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getResName() { return resName; } public void setResName(String resName) { this.resName = resName; } @ManyToOne @JoinColumn(name = "parent_id") @JsonIgnore public Resource getParent() { return parent; } public void setParent(Resource parent) { this.parent = parent; } /* * 级联删除 * @return */ @OneToMany(cascade={CascadeType.REMOVE},mappedBy = "parent") public List<Resource> getChildren() { return children; } public void setChildren(List<Resource> children) { this.children = children; } }
sys_user
1 user $2a$10$cr.EMPhNHbBX8Xksa62gGeo1cwPjk0rp4tDc5ICLAhY8.y1BHUjxK
2 admin $2a$10$cr.EMPhNHbBX8Xksa62gGeo1cwPjk0rp4tDc5ICLAhY8.y1BHUjxK
sys_role
1 user 普通用户
2 admin 管理员
sys_resource
1 用户页面 /userPage
2 管理员页面 /adminPage
3 所有人可看页面 /allPage
sys_user_role
1 1
2 2
sys_role_resource
1 1
2 2
简单描述一下
两个用户 code:user password:111111
code:admin password:111111
user 拥有user 普通用户角色
admin 拥有admin管理员角色
user角色拥有 /userPage 资源(即user用户可以访问该资源)
admin角色拥有 /adminPage 资源 (即admin用户可以访问该资源)
package com.youxiu326.service; import com.youxiu326.bean.Resource; import com.youxiu326.bean.Role; import java.util.List; public interface ResourceService { /** * 根据访问资源路径 查询资源对象 * @param url 资源路径 * @return */ Resource getResourceByUrl(String url); /** * 根据资源id 查询所有拥有该资源的角色 * @param resourceId 资源id * @return */ List<Role> getRoles(String resourceId); }
package com.youxiu326.service; import com.youxiu326.bean.Role; import java.util.List; public interface RoleService { /** * 根据用户code查询该用户拥有的所有角色 * @param userName 用户code * @return */ List<Role> getRolesOfUser(String userName); }
package com.youxiu326.service.impl; import com.youxiu326.bean.Resource; import com.youxiu326.bean.Role; import com.youxiu326.repository.ResourceDao; import com.youxiu326.service.ResourceService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class ResourceServiceImpl implements ResourceService { @Autowired private ResourceDao resourceDao; @Override public Resource getResourceByUrl(String url) { return resourceDao.findByUrl(url); } @Override public List<Role> getRoles(String resourceId) { return resourceDao.findRolesOfResource(resourceId); } }
package com.youxiu326.service.impl; import com.youxiu326.bean.Role; import com.youxiu326.repository.RoleDao; import com.youxiu326.service.RoleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class RoleServiceImpl implements RoleService { @Autowired private RoleDao roleDao; @Override public List<Role> getRolesOfUser(String userName) { return roleDao.findRolesByUser(userName); } }
package com.youxiu326.repository; import com.youxiu326.bean.Resource; import com.youxiu326.bean.Role; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; /** * Created by lihui on 2019/1/29. */ public interface ResourceDao extends JpaRepository<Resource, String> { @Query("select distinct r.resources from User as o left join o.roles as r where o.id = ?1") public List<Resource> findResourcesByOperator(String operatorId); Resource findByUrl(String url); //自定义sql语句并且开启本地sql //根据用户名查找该用户所有权限 //@Query(value = "select r.* from role r, user_role ur where ur.username = ?1 and ur.rid = r.id", nativeQuery = true) //public List<Role> findRolesOfUser(String username); //根据resource的主键查找resource允许的所有权限 @Query("select r from Role r left join r.resources as rs where rs.id=?1 ") public List<Role> findRolesOfResource(String resourceId); }
package com.youxiu326.repository; import com.youxiu326.bean.Role; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; public interface RoleDao extends JpaRepository<Role, String> { /* @Query("select distinct r.resources from Operator as o left join o.roles as r where o.id = ?1") public List<Resource> findResourcesByOperator(String operatorId); */ @Query("select distinct r.roles from User as r where r.userName=?1") List<Role> findRolesByUser(String userName); }
package com.youxiu326.repository; import com.youxiu326.bean.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface UserDao extends JpaRepository<User, String> { List<User> findByUserName(String userName); }
为框架实现UserDetails接口和UserDetailsService接口在我们的程序中,必须要有一个类,实现UserDetailsService这个接口并且重写它的loadUserByUsername(String s)这个函数。另外也必须要有一个类,实现UserDetails接口并重写它其中的几个方法。为什么呢?这涉及到Spring Security框架的认证的原理。在用户登录的时候,程序将用户输入的的用户名和密码封装成一个类对象。然后根据用户名去数据库中查找用户的数据,封装成一个类对象放在内存中。注意,一个是用户输入的数据,一个是数据库中的数据。将两个对象比对,如果密码正确,就把用户信息的封装(包含着身份信息、细节信息等)存到SecurityContextHolder中(类似Session),使用的时候还要取出来。而这个过程中,从数据库中取出的用户信息的封装不是简单的User实例,而是一个实现了UserDetails这个接口的类的对象,这个对象里面不仅有用户的账号密码信息,还有一些判断账号是否可用、判断账号是否过期、判断账号是否被锁定的函数。在验证过程中,负责根据用户输入的用户名返回数据库中用户信息的封装这个功能的就是Service,它实现了UserDetailsService,重写了它的loadUserByUsername(String s)方法,这个方法就是根据用户名返回了UserDetails的一个具体实现
package com.youxiu326.security; import com.youxiu326.bean.Role; import com.youxiu326.bean.User; 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.List; //一定要有一个类,实现UserDetails接口,供程序调用 public class UserDetailsImpl implements UserDetails { private String username; private String password; //包含着用户对应的所有Role,在使用时调用者给对象注入roles private List<Role> roles; public UserDetailsImpl() { } //用User构造 public UserDetailsImpl(User user) { this.username = user.getUserName(); this.password = user.getPassword(); } //用User和List<Role>构造 public UserDetailsImpl(User user, List<Role> roles) { this.username = user.getUserName(); this.password = user.getPassword(); this.roles = roles; } @Override //返回用户所有角色的封装,一个Role对应一个GrantedAuthority public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); for(Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getRoleName())); } return authorities; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override //判断账号是否已经过期,默认没有过期 public boolean isAccountNonExpired() { return true; } @Override //判断账号是否被锁定,默认没有锁定 public boolean isAccountNonLocked() { return true; } @Override //判断信用凭证是否过期,默认没有过期 public boolean isCredentialsNonExpired() { return true; } @Override //判断账号是否可用,默认可用 public boolean isEnabled() { return true; } }
UserDetailsService也需要被实现,我们在写UserService时直接实现这个接口就可以。所以UserService跟其他Service有些不同
package com.youxiu326.security; import com.youxiu326.bean.User; import com.youxiu326.repository.UserDao; import com.youxiu326.service.RoleService; 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 javax.transaction.Transactional; import java.util.List; /** * 简单来讲就是程序接收到了用户输入的用户名,交给了UserService,它根据用户名去数据库中取到用户的信息, * 封装到实体类User的实例中,然后使用该User实例,再利用RoleService(封装了RoleRopository)查出该User对用的roles, * 构造一个UserDetailsImpl的对象,把这个对象返回给程序 */ @Service //框架需要使用到一个实现了UserDetailsService接口的类 public class MyUserDetailsService implements UserDetailsService{ @Autowired private UserDao userRepository; @Autowired private RoleService roleService; @Transactional public List<User> getAllUser() { return userRepository.findAll(); } @Transactional public List<User> getByUsername(String userName) { return userRepository.findByUserName(userName); } @Override //重写UserDetailsService接口里面的抽象方法 //根据用户名 返回一个UserDetails的实现类的实例 public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { System.out.println("查找用户:" + s); List<User> list = getByUsername(s); if (list==null || list.size()==0){ throw new UsernameNotFoundException("没有该用户"); } User user = getByUsername(s).get(0); if(user == null) { throw new UsernameNotFoundException("没有该用户"); } //查到User后将其封装为UserDetails的实现类的实例供程序调用 //用该User和它对应的Role实体们构造UserDetails的实现类 return new UserDetailsImpl(user, roleService.getRolesOfUser(user.getUserName())); } }
1.写一个类,实现FilterInvocationSecurityMetadataSource这个接口,供系统调用,放在Component包中。作用是在用户请求一个地址的时候,截获这个地址,告诉程序访问这个地址需要哪些权限角色。
package com.youxiu326.security; import com.youxiu326.bean.Resource; import com.youxiu326.bean.Role; import com.youxiu326.service.ResourceService; 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.stereotype.Component; import java.util.Collection; import java.util.List; @Component //接收用户请求的地址,返回访问该地址需要的所有权限 public class MyFilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource { @Autowired private ResourceService resourceService; @Override //接收用户请求的地址,返回访问该地址需要的所有权限 public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { //得到用户的请求地址,控制台输出一下 String requestUrl = ((FilterInvocation) o).getRequestUrl(); System.out.println("用户请求的地址是:" + requestUrl); //如果登录页面就不需要权限 if ("/login".equals(requestUrl)) { return null; } Resource resource = resourceService.getResourceByUrl(requestUrl); //如果没有匹配的url则说明大家都可以访问 if(resource == null) { return SecurityConfig.createList("ROLE_LOGIN"); } //将resource所需要到的roles按框架要求封装返回(ResourceService里面的getRoles方法是基于RoleRepository实现的) List<Role> roles = resourceService.getRoles(resource.getId()); int size = roles.size(); String[] values = new String[size]; for (int i = 0; i < size; i++) { values[i] = roles.get(i).getRoleName(); } return SecurityConfig.createList(values); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return false; } }
2.写一个类,实现AccessDecisionManager这个接口 这个类的作用是接收上面那个类返回的访问当前url所需要的权限列表(decide方法的第三个参数),再结合当前用户的信息(decide方法的第一个参数),决定用户是否可以访问这个url
package com.youxiu326.security; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.BadCredentialsException; 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; @Component //Security需要用到一个实现了AccessDecisionManager接口的类 //类功能:根据当前用户的信息,和目标url涉及到的权限,判断用户是否可以访问 //判断规则:用户只要匹配到目标url权限中的一个role就可以访问 public class MyAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException { //迭代器遍历目标url的权限列表 Iterator<ConfigAttribute> iterator = collection.iterator(); while (iterator.hasNext()) { ConfigAttribute ca = iterator.next(); String needRole = ca.getAttribute(); if ("ROLE_LOGIN".equals(needRole)) { if (authentication instanceof AnonymousAuthenticationToken) { throw new BadCredentialsException("未登录"); } else return; } //遍历当前用户所具有的权限 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (GrantedAuthority authority : authorities) { if (authority.getAuthority().equals(needRole)) { return; } } } //执行到这里说明没有匹配到应有权限 throw new AccessDeniedException("权限不足!"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
3.写一个类,实现AccessDeniedHandler这个接口 作用是自定义403响应内容。
package com.youxiu326.security; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component //自定义403响应内容 public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=utf-8"); PrintWriter out = response.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}"); out.flush(); out.close(); } }
4.写一个Spring Security配置类,继承WebSecurityConfigurerAdapter类
package com.youxiu326.config;
import com.youxiu326.security.MyAccessDecisionManager;
import com.youxiu326.security.MyAccessDeniedHandler;
import com.youxiu326.security.MyFilterInvocationSecurityMetadataSourceImpl;
import com.youxiu326.security.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Configuration
@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true) //开启方法权限控制
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
//根据一个url请求,获得访问它所需要的roles权限
@Autowired
private MyFilterInvocationSecurityMetadataSourceImpl myFilterInvocationSecurityMetadataSource;
//接收一个用户的信息和访问一个url所需要的权限,判断该用户是否可以访问
@Autowired
private MyAccessDecisionManager myAccessDecisionManager;
//403页面
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
/**定义认证用户信息获取来源,密码校验规则等*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**有以下几种形式,使用第3种*/
//inMemoryAuthentication 从内存中获取
//auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123123")).roles("USER");
//jdbcAuthentication从数据库中获取,但是默认是以security提供的表结构
//usersByUsernameQuery 指定查询用户SQL
//authoritiesByUsernameQuery 指定查询权限SQL
//auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);
//注入userDetailsService,需要实现userDetailsService接口 并指定加密方式BCryptPasswordEncoder
auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
//在这里配置哪些页面不需要认证
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/", "/noAuthenticate","/**/*.ico","/**/*.js");
}
/**定义安全策略*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //配置安全策略
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
// .antMatchers("/hello").hasAuthority("ADMIN")
//.antMatchers("/js/**","/css/**","/images/*","/fonts/**","/**/*.png","/**/*.jpg","/**/*.ico").permitAll()
.and()
.formLogin() //配置登录页面
.loginPage("/login") //自定义登录页面
.loginProcessingUrl("/user/login") //自定义的登录接口
.usernameParameter("username") //指定页面中对应用户名的参数名称
.passwordParameter("password") //指定页面中对应密码的参数名称
.permitAll() //配置登录页面所有人可以访问
.failureHandler(new AuthenticationFailureHandler() {//登陆失败后的操作
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
StringBuffer sb = new StringBuffer();
sb.append("{\"status\":\"error\",\"msg\":\"");
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
sb.append("用户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
sb.append("账户被禁用,登录失败,请联系管理员!");
} else {
sb.append("登录失败!");
}
sb.append("\"}");
out.write(sb.toString());
out.flush();
out.close();
}
})
.successHandler(new AuthenticationSuccessHandler() {//登录成功后的操作
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
// ObjectMapper objectMapper = new ObjectMapper();
//String s = "{\"status\":\"success\",\"msg\":" + "}";
String s = "{\"status\":\"200\",\"data\":\"success\",\"msg\":\"登录成功\"}";
out.write(s);
out.flush();
out.close();
}
})
.and()
.logout() //登出
.permitAll() //所有人可以登出
.and()
.csrf()
.disable()
.exceptionHandling() //配置自定义403响应
.accessDeniedHandler(myAccessDeniedHandler);
}
}
所有配置类都已写完,写页面 和 Controller
package com.youxiu326.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class SecurityController { /** * 自定义登录界面 * @return */ @GetMapping(value = "/login") public String login(){ return "login"; } /** * admin角色可访问 * @return */ @GetMapping(value = "/adminPage") public String adminPage(){ return "adminPage"; } /** * user角色可访问 * @return */ @GetMapping(value = "/userPage") public String userPage(){ return "userPage"; } /** * 所有人可访问 该资源在资源表中无记录 * @return */ @GetMapping(value = "/allPage") public String allPage(){ return "/allPage"; } }
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <base th:href="${#httpServletRequest.getContextPath()+'/'}"> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>自定义登录页面</h2> <form action="#" method="post"> <table> <tr> <td>用户名:</td> <td><input type="text" id="username" name="username"></td> </tr> <tr> <td>密码:</td> <td><input type="password" id="password" name="password"></td> </tr> <tr> <td colspan="2"><button type="button" onclick="login()">登录</button></td> </tr> </table> </form> </body> <script src="/jquery-1.11.3.min.js"></script> <!--<script th:src="@{/jquery-1.11.3.min.js}"></script>--> <script> function login(){ $.ajax({ type: 'POST', url: "/user/login", data: {"username":$("#username").val(),"password":$("#password").val()}, // dataType: "json", success: function(response){ if(response.data=="success"){ window.location.href="allPage"; }else { alert(response.msg); } console.log(response); }, error:function(response){ alert("失败"); console.log(response); } }); } </script> </html>
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <base th:href="${#httpServletRequest.getContextPath()+'/'}"> <meta charset="UTF-8"> <title>所有人可看页面</title> </head> <body> <h2>这是allPage页面</h2> <br/> <a href="userPage" target="_blank">去用户界面</a> <br/> <a href="adminPage" target="_blank">去管理员界面</a> </body> </html>
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <base th:href="${#httpServletRequest.getContextPath()+'/'}"> <meta charset="UTF-8"> <title>用户页面界面</title> </head> <body> <h2>这是userPage页面</h2> </body> </html>
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <base th:href="${#httpServletRequest.getContextPath()+'/'}"> <meta charset="UTF-8"> <title>所有人可看页面</title> </head> <body> <h2>这是allPage页面</h2> <br/> <a href="userPage" target="_blank">去用户界面</a> <br/> <a href="adminPage" target="_blank">去管理员界面</a> </body> </html>
大功告成,测试开始
1.未登录时访问资源路径 自动跳转至登陆页面(localhost:8080/login)
2.输入错误的用户名or密码 提示错误信息
3.登陆时请求路径(localhost:8080/user/login)
4.登陆后user用户可以访问http://localhost:8080/userPage 但是无权访问http://localhost:8080/adminPage
5.未登录时是可以访问http://localhost:8080/allPage (因为该资源未在资源表中有记录)
github: https://github.com/youxiu326/sb_security.git