这里是一个学习过程笔记的汇总:Spring Boot学习汇总
学习之前,需要对Spring Security有个大概了解,可以去看官方文档:Spring Security官方文档
首先创建一个Spring Boot项目,导入web、Security(这里导入的是5.1.2版本)以及JPA模块。整合JPA操作请看这篇:Spring Boot整合jpa相关操作
项目结构如下:
首先创建相关数据表:
// 用户表
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
// 角色表
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
// 用户和角色关联表
CREATE TABLE `user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`u_id` bigint(20) NOT NULL,
`r_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
// 权限表
CREATE TABLE `resource` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`url` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`re_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
// 角色和权限关联表
CREATE TABLE `role_resource` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`r_id` bigint(20) NOT NULL,
`re_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
主配置文件application.yml中增加数据库连接等相关配置:
server:
port: 8080
servlet:
context-path: /mysecurity
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot_test?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: xxxxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: update
创建相关的实体类:
package com.example.springbootsecuritytest.bean;
import javax.persistence.*;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
public Long getId() {
return id;
}
public void setId(Long 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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
package com.example.springbootsecuritytest.bean;
import javax.persistence.*;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue
private Long id;
@Column(name = "role_name")
private String roleName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
package com.example.springbootsecuritytest.bean;
import javax.persistence.*;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
@Entity
@Table(name = "user_role")
public class UserRole {
@Id
@GeneratedValue
private Long id;
@Column(name = "u_id")
private Long uId;
@Column(name = "r_id")
private Long rId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getuId() {
return uId;
}
public void setuId(Long uId) {
this.uId = uId;
}
public Long getrId() {
return rId;
}
public void setrId(Long rId) {
this.rId = rId;
}
}
package com.example.springbootsecuritytest.bean;
import javax.persistence.*;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
@Entity
@Table(name = "resource")
public class Resource {
@Id
@GeneratedValue
private Long id;
private String url;
@Column(name = "re_name")
private String reName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getReName() {
return reName;
}
public void setReName(String reName) {
this.reName = reName;
}
}
package com.example.springbootsecuritytest.bean;
import javax.persistence.*;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
@Entity
@Table(name = "role_resource")
public class RoleResource {
@Id
@GeneratedValue
private Long id;
@Column(name = "re_id")
private Long reId;
@Column(name = "r_id")
private Long rId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getReId() {
return reId;
}
public void setReId(Long reId) {
this.reId = reId;
}
public Long getrId() {
return rId;
}
public void setrId(Long rId) {
this.rId = rId;
}
}
除了实体类,还创建要给类来实现UserDetails接口,并实现该接口的 getAuthorities()方法,封装用户所拥有的所有权限。
package com.example.springbootsecuritytest.bean;
import com.example.springbootsecuritytest.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Autowired
private RoleService roleService;
public void setRoles(List<Role> roles) {
this.roles = 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;
}
public List<Role> getRoles()
{
return 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;
}
@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;
}
}
创建相关的dao接口:
package com.example.springbootsecuritytest.dao;
import com.example.springbootsecuritytest.bean.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
public interface UserDao extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
List<User> findByUsername(@Param("username") String username);
}
package com.example.springbootsecuritytest.dao;
import com.example.springbootsecuritytest.bean.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
public interface RoleDao extends JpaRepository<Role, Long>, JpaSpecificationExecutor<Role> {
// 根据用户名查找所有的权限
@Query(value = "select r.* from role r, user_role ur where ur.r_id = r.id and ur.u_id = ?1", nativeQuery = true)
List<Role> findRolesByUserId(@Param("userId") Long userId);
// 根据权限id查找所有的角色
@Query(value = "select r.* from role r, role_resource rr where rr.r_id = r.id and rr.re_id = ?1", nativeQuery = true)
List<Role> findRolesByResourceId(@Param("resourceId") Long resourceId);
}
package com.example.springbootsecuritytest.dao;
import com.example.springbootsecuritytest.bean.Resource;
import com.example.springbootsecuritytest.bean.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
public interface ResourceDao extends JpaRepository<Resource, Long>, JpaSpecificationExecutor<Resource> {
@Query("from Resource where url =:url")
Resource getResourceByUrl(@Param("url") String url);
}
package com.example.springbootsecuritytest.dao;
import com.example.springbootsecuritytest.bean.RoleResource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
public interface RoleResourceDao extends JpaRepository<RoleResource, Long>, JpaSpecificationExecutor<RoleResource> {
}
相关的service:
package com.example.springbootsecuritytest.service;
import com.example.springbootsecuritytest.bean.User;
import com.example.springbootsecuritytest.bean.UserDetailsImpl;
import com.example.springbootsecuritytest.dao.UserDao;
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;
/**
* @author junxiang
* @date 2018/12/18 0018
*/
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Autowired
private RoleService roleService;
@Transactional
public List<User> getAllUser(){
return userDao.findAll();
}
@Transactional
public List<User> getByUsername(String username){
return userDao.findByUsername(username);
}
public void saveUser(User user) {
userDao.save(user);
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("查找用户:" + s);
User user = getByUsername(s).get(0);
if(user == null){
throw new UsernameNotFoundException("没有该用户");
}
System.out.println("查到用户; " + user);
//查到User后将其封装为UserDetails的实现类的实例供程序调用
//用该User和它对应的Role实体们构造UserDetails的实现类
return new UserDetailsImpl(user, roleService.findRolesByUserId((user.getId())));
}
}
这个UserService比较特殊,实现了UserDetailsService接口,实现了loadUserByUsername方法,返回结果是UserDetails,上面我们已经创建了UserDetailsImpl类来实现这个接口:当用户登录时,就会走这个loadUserByUsername方法,通过用户名,查找用户的相关信息并封装成一个UserDetails(包括用户名,密码,拥有的所有权限)。
接下来创建一个过滤器:
package com.example.springbootsecuritytest.component;
import com.example.springbootsecuritytest.bean.Resource;
import com.example.springbootsecuritytest.bean.Role;
import com.example.springbootsecuritytest.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;
/**
* @author junxiang
* @date 2018/12/18 0018
* 用户请求一个地址的时候,截获这个地址,返回访问该地址需要的所有权限
*/
@Component
public class FilterInvocationSecurityMetadataSourceImpl 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 ("/index".equals(requestUrl) || "/register".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;
}
}
这个过滤器中有个getAttributes()方法,当启动项目后,浏览器输入的每个请求地址,都会被拦截下来,若判断是自定义的不需要权限即可操作的页面路径,则直接访问,否则继续判断访问该路径所需要的角色。
再创建一个管理器:
package com.example.springbootsecuritytest.component;
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;
/**
* @author junxiang
* @date 2018/12/18 0018
* Security需要用到一个实现了AccessDecisionManager接口的类
*
* 类功能:这个类的作用是接收FilterInvocationSecurityMetadataSourceImpl类返回的访问当前url所需要的权限列表(decide方法的第三个参数),
* 再结合当前用户的信息(decide方法的第一个参数),决定用户是否可以访问这个url。
* 判断规则:用户只要匹配到目标url权限中的一个role就可以访问
*/
@Component
public class AccessDecisionManagerImpl implements AccessDecisionManager {
/**
*
* @param authentication 当前用户的信息
* @param o
* @param collection url权限列表
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@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 false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
这个管理器,主要是判断当前登陆用户有没有权限访问资源,主要是decide()方法,根据当前登陆用户所拥有的角色,和要访问资源的所需角色相对比,有一个符合即可访问,看上面的代码应该能看得懂的。
接下来是最重要的一个配置类:
package com.example.springbootsecuritytest.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
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.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
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;
/**
* @author junxiang
* @date 2018/12/19 0019
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userService;
// 根据一个url请求,获得访问它所需要的roles权限
@Autowired
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
// 接收一个用户的信息和访问一个url所需要的权限,判断该用户是否可以访问
@Autowired
private AccessDecisionManager accessDecisionManager;
/**
* 定义认证用户信息获取来源,密码校验规则等
* @param auth
* @throws Exception
*/
@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接口
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
/**
* 在这里配置哪些页面不需要认证
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/register");
}
/**
* 定义安全策略
* @param http
* @throws Exception
*/
@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(filterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(accessDecisionManager);
return o;
}
})
// .antMatchers("/hello").hasAuthority("ADMIN")
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException, IOException {
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\":" + "}";
out.write(s);
out.flush();
out.close();
}
})
.and()
.logout() // 退出
.permitAll()
.and()
.csrf()
.disable()
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
out.flush();
out.close();
}
});
}
}
这个类是安全策略类,实现了WebSecurityConfigurerAdapter接口,有三个configure方法,
第一个configure:参数为AuthenticationManagerBuilder,这个方法主要用来定义用户认证时的信息获取来源(即我们在前面定义的UserService类的的loadUserByUsername()方法)以及密码校验规则(采用的是Spring Security特有的BCryptPasswordEncoder加密);
第二个configure:用来配置哪些页面不要认证,比如注册等。
第三个configure:定义安全策略,配置上前面创建的过滤器以及管理器,还有登陆请求的路径,默认是"/login",以及默认的用户名密码参数名称,还有登陆成功、登陆失败以及权限不够时的处理器,这些处理器可以分别拿出来写成一个单独的类。还有配置退出登陆,即logout()方法。
再创建一个controller控制器:
package com.example.springbootsecuritytest.controller;
import com.example.springbootsecuritytest.bean.User;
import com.example.springbootsecuritytest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @author junxiang
* @date 2018/12/19 0019
*/
@Controller
public class SecurityController {
@Autowired
private UserService userService;
@GetMapping(value = "/index")
public String index() {
System.out.println("进来登陆了...");
return "myLoginPage";
}
@GetMapping(value = "/register")
public String register() {
System.out.println("开始注册...");
return "register";
}
@GetMapping(value = "/product")
public String product() {
System.out.println("产品...");
return "product";
}
/**
* 注册
* @param user
*/
@PostMapping("/register-user")
public String registerUser(User user) {
String encodePassword = new BCryptPasswordEncoder().encode(user.getPassword().trim());
user.setPassword(encodePassword);
userService.saveUser(user);
System.out.println("注册成功");
return "registerSuccess";
}
}
包含注册以及一些跳转请求,这个注册时输入的密码直接就使用BCryptPasswordEncoder进行加密再入库。
再写几个简单的h5页面:
myLoginPage.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div class="bg">
<div class="contain">
<div>
<a class="title">登陆页</a>
</div>
<div>
<form action="/mysecurity/login" method="post">
<div class="username">
<input name="username" type="text" placeholder="输入账号">
</div>
<div class="password">
<input name="password" type="password" placeholder="输入密码">
</div>
<div>
<button type="submit" class="loginbutton">登录</button>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
product.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<!--<head>-->
<!--<link rel="stylesheet" href="css/login.css">-->
<!--</head>-->
<body>
<div class="bg">
<div class="contain">
<div>
<a class="title">产品页</a>
</div>
<div>
<h1>这是产品页面!!!我有权限访问了</h1>
</div>
</div>
</div>
</div>
</body>
register.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div id='mask'>
<div class="whiteBg">
<div class="title">注册</div>
<form action="/mysecurity/register-user" method="post">
<div class="phoneBg">
<input class="registerInput" name="username" id="username" placeholder="请输入手机号" type="number">
<input id="inviter" type="hidden">
</div>
<div class="pwdBg">
<input class="registerInput" name="password" id="password" placeholder="请设置密码" type="password">
</div>
<div>
<button type="submit" class="loginbutton">注册</button>
</div>
</form>
</div>
</div>
</body>
</html>
registerSuccess.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div class="bg">
<div class="contain">
<div>
<a class="title">注册成功</a>
</div>
<div>
<h1>注册成功!!!</h1>
</div>
</div>
</div>
</div>
</body>
到此,整个案例的代码都贴出来了,
下面具体测试一下,先添加相关路径到resource表,如下:
role中设置两个角色,如下:
role和resource关联表增加数据:
即DBA角色没有访问product的权限,而ADMIN有。
启动项目,启动过程中会进行以下操作:
会先访问安全配置类的中的configure方法,定义用户认证信息的获取来源以及密码校验规则,
紧接着会继续访问安全配置类进行安全策略定义:
接下来会获取已配置的不需要认证的访问路径:
启动完成,请求注册页面: http://localhost:8080/mysecurity/register
我们已经在安全配置类中设置注册页面的请求接口不需要认证:
所以请求后直接跳转到注册页面:
注入用户名和密码点击注册,跳转到controller:
密码已经加密。
注册成功,给这个用户添加角色DBA,即没有访问product的权限,
再注册一个用户,分给他ADMIN的角色,即拥有访问product的权限。
两个用户:
用户权限关联表:
请求登陆页面:http://localhost:8080/mysecurity/index
可见已经配置了登陆页不需要权限即可访问,
使用拥有product访问权限的用户登陆:
登陆时会先走到定义的UserService类的loadUserByUsername(String s)方法
这个是调用的UserDetailsImpl类的构造函数进行返回,返回的结果就是用户的相关信息,包括用户名,密码,以及用户所拥有的角色,如下,进入到UserDetailsImpl类中getAuthorities()方法进行用户的角色封装
完了后就进入到安全配置类中,登陆成功:
登陆成功后我们来访问product,这个用户是有权限的: http://localhost:8080/mysecurity/product
可见,访问product需要的角色是ADMIN。
再往下就到AccessDecisionManagerImpl类中进行角色匹配,若匹配上,访问product成功,若没有匹配上,返回权限不足。
可见是匹配上了,看浏览器:
下面用另外一个用户登陆,登陆后访问product
可见用户所拥有的角色和访问资源所需的角色不匹配:
下面看一下如果没有登陆的情况下去访问product,会怎么弄?
没登录时直接访问,会直接跳转到login页面,让登陆。
好了,已上就是Spring Security的认证,权限操作,下次再研究下基于注解的权限控制。