SpringSecurity+Jpa在前后端分离项目中的实现(含接入数据库部分)
一、前言
本篇只是简单的Spring Security前后端分离的小Demo,不细讲代码,得对SpringSecurity有一定了解才能看得懂。
二、基本思路
服务端通过 JSON
字符串,告诉前端用户是否登录、认证;前端根据这些提示跳转对应的登录页、认证页
三、具体实现
-
pom依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
-
配置文件,使用Jpa:
spring:
datasource:
url: jdbc:mysql://localhost:3306/security_authority?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
-
工具类
@Data @NoArgsConstructor @AllArgsConstructor public class AjaxResponseBody { //返回体中返回的状态码 private String status; //返回的错误信息 private String msg; }
各种Handle用于前后端交互,在Spring Security配置类中被引用,如登陆成功失败、注销的Handle类
前后端交互的Handle
原理:特定状态下返回一个特定的Json格式的数据给前端
-
未登录
@Component public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException { AjaxResponseBody responseBody = new AjaxResponseBody(); responseBody.setStatus("000"); responseBody.setMsg("无访问权限,请先登录"); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.getWriter().write(JSON.toJSONString(responseBody)); } }
-
无权访问
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("300");
responseBody.setMsg("无权访问");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
-
登陆失败
@Component public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException { AjaxResponseBody responseBody = new AjaxResponseBody(); responseBody.setStatus("400"); responseBody.setMsg("登录失败"); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.getWriter().write(JSON.toJSONString(responseBody)); } }
-
登陆成功
@Component public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { AjaxResponseBody responseBody = new AjaxResponseBody(); responseBody.setStatus("200"); responseBody.setMsg("登录成功"); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.getWriter().write(JSON.toJSONString(responseBody)); } }
-
注销成功
@Component public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { AjaxResponseBody responseBody = new AjaxResponseBody(); responseBody.setStatus("100"); responseBody.setMsg("注销成功"); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.getWriter().write(JSON.toJSONString(responseBody)); } }
实体类及其业务代码
-
用户类
@Getter
@Setter
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "uid")
private String uid;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Getter
@Setter
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "uid")
private String uid;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
/*
角色和用户是多对多的关系
*/
@ManyToMany(cascade=CascadeType.REFRESH,fetch = FetchType.EAGER)
@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(name = "uid",referencedColumnName = "uid"),
inverseJoinColumns = @JoinColumn(name = "rid",referencedColumnName = "rid"))
private List<SysRole> roles;
/*
继承了UserDetails类后会默认继承一些方法
下面是获取用户的角色的方法,返回的角色(权限会在登陆成功后存在令牌中)
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(SysRole role : roles){
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleName());
authorities.add(authority);
}
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@ManyToMany(cascade=CascadeType.REFRESH,fetch = FetchType.EAGER)
@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(name = "uid",referencedColumnName = "uid"),
inverseJoinColumns = @JoinColumn(name = "rid",referencedColumnName = "rid"))
private List<SysRole> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(SysRole role : roles){
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleName());
authorities.add(authority);
}
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
-
角色类
@Getter
@Setter
@Entity
@Table(name = "sys_role")
public class SysRole implements Serializable, GrantedAuthority {
private static final long serialVersionUID = 4395377670162987328L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "rid")
private Long rid;
@Column(name = "role_name")
private String roleName;
@Column(name = "role_desc")
private String roleDesc;
@ManyToMany(mappedBy = "roles",fetch = FetchType.LAZY)
private List<SysUser> users;
/*
角色和url也是多对多的关系
*/
@ManyToMany(cascade = CascadeType.REFRESH,fetch = FetchType.LAZY)
@JoinTable(joinColumns = @JoinColumn(name = "rid",referencedColumnName = "rid")
,inverseJoinColumns = @JoinColumn(name = "mid",referencedColumnName = "mid"))
private List<SysMenu> menus;
@Override
public String getAuthority() {
return roleName;
}
}
-
许可类
@Getter @Setter @Entity @Table(name = "sys_menu") public class SysMenu implements Serializable { private static final long serialVersionUID = -1808565992831740300L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "mid") private Long mid; @Column(name = "url") private String url; @ManyToMany(mappedBy = "menus",fetch = FetchType.EAGER) private List<SysRole> roles; }
-
DAO层
-
userDao
@Repository public interface UserDao extends JpaRepository<SysUser,String> , JpaSpecificationExecutor<SysUser> { /** * 通过用户名获取用户实体 * @param userName * @return */ SysUser findUserByUsername(String userName); }
-
RoleDao
@Repository public interface RoleDao extends JpaRepository<SysRole,Long>, JpaSpecificationExecutor<SysRole> { /** * 添加一个角色 * @param uid * @param rid * @return */ @Modifying @Query(value = "insert into sys_user_role values(?1,?2)",nativeQuery = true) int grant(String uid,Long rid); /** * 删除一个角色 * @param uid * @param rid * @return */ @Modifying @Query(value = "delete from sys_user_role where uid = ?1 and rid = ?2",nativeQuery = true) int revoke(String uid,Long rid); /** * 获取该角色下的url * @param Url * @return */ @Query(value = "select r.role_name from sys_role r,sys_role_menus rm,sys_menu m where rm.rid = r.rid and rm.mid = m.mid and m.url =?1",nativeQuery = true) List<String> selectRoleNameByUrl(String Url); }
-
-
Service层
-
UserServiceImpl
@Service public class UserServiceImpl implements UserService { @Autowired UserDao userDao; @Override public void saveUser(SysUser user) { String password = user.getPassword(); //新建用户时加密密码 String encodePassword = new BCryptPasswordEncoder().encode(password); user.setPassword(encodePassword); //分配id user.setUid(IdUtil.simpleUUID()); userDao.save(user); } @Override public UserDetails loadUserByUserName(String username) { return userDao.findUserByUsername(username); } }
-
RoleServiceImpl
@Transactional @Service public class RoleServiceImpl implements RoleService { @Autowired RoleDao roleDao; @Override public boolean grant(String uid, Long rid) { if(roleDao.grant(uid,rid) > 0){ return true; } return false; } @Override public boolean revoke(String uid, Long rid) { if(roleDao.revoke(uid,rid) > 0){ return true; } return false; } @Override public void saveRole(SysRole role) { roleDao.save(role); } @Override public List<String> getRoleNameByUrl(String url) { return roleDao.selectRoleNameByUrl(url); } }
-
-
Controller层
-
UserController
@RestController @RequestMapping("/user") public class UserController { @Autowired UserService userService; @GetMapping({"/","/index"}) public String index(){ return "index"; } @GetMapping("/root") public String toRoot(){ return "root"; } @GetMapping("/manager") public String toManager(){ return "manager"; } @PostMapping("/register") public String register(SysUser user){ userService.saveUser(user); return "register"; } }
-
RoleController
@RestController @RequestMapping("/role") public class RoleController { @Autowired RoleService roleService; @PostMapping public String save(SysRole role){ roleService.saveRole(role); return "saveRole"; } /** * 给角色授权 * @param uid * @param rid * @return */ @PutMapping("/grant") public String grant(String uid,Long rid){ if(roleService.grant(uid, rid)){ return "success"; }else { return "failure"; } } /** * 撤销角色权限 * @param uid * @param rid * @return */ @DeleteMapping("/revoke") public String revoke(String uid,Long rid){ if(roleService.revoke(uid, rid)){ return "success"; }else { return "failure"; } }
-
Jpa会根据实体之间的关系自动创建数据库
认证的Provider
@Component
public class SelfAuthenticationProvider implements AuthenticationProvider {
@Autowired
UserService userService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取用户登录输入的信息
String userName = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
//用注册用户时相同的加密方式加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//根据用户名获取数据库中的用户
UserDetails userInfo = userService.loadUserByUserName(userName);
//进行密码比对
if (!encoder.matches(password,userInfo.getPassword())) {
throw new BadCredentialsException("用户名密码不正确,请重新登陆!");
}
//比对成功返回token令牌
return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
}
/**
* 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
* @param authentication
* @return
*/
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
Security的配置类
拦截和认证执行的地方
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 未登陆时返回 JSON 格式的数据给前端(否则为 html)
@Autowired
AjaxAuthenticationEntryPoint authenticationEntryPoint;
// 登录成功返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxAuthenticationSuccessHandler authenticationSuccessHandler;
// 登录失败返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxAuthenticationFailureHandler authenticationFailureHandler;
// 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
@Autowired
AjaxLogoutSuccessHandler logoutSuccessHandler;
// 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
@Autowired
AjaxAccessDeniedHandler accessDeniedHandler;
// 自定义安全认证
@Autowired
SelfAuthenticationProvider provider;
//安全认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider);
}
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭跨域拦截
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/","/user/register").permitAll()
/*.antMatchers("/user/manager").hasAnyRole("MANAGER","ROOT")
将授权操作分配root权限
.antMatchers("/user/root","/role/grant").hasRole("ROOT")*/
//根据角色动态分配路由
.withObjectPostProcessor(new DefinedObjectPostProcessor())
//配置决策方式
.accessDecisionManager(accessDecisionManager())
.and()
.exceptionHandling()
//未登录拦截
.authenticationEntryPoint(authenticationEntryPoint)
//无权访问拦截
.accessDeniedHandler(accessDeniedHandler)
//登录
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.permitAll()
//注销
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler);
}
/**
* AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
* ConsensusBased – 同意投票多于拒绝投票(忽略弃权回答)则允许访问
* UnanimousBased – 每个投票者选择弃权或同意则允许访问
*
* 决策管理
*/
private AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
decisionVoters.add(new WebExpressionVoter());
decisionVoters.add(new AuthenticatedVoter());
/* decisionVoters.add(new RoleVoter());*/
/* 路由权限管理 */
decisionVoters.add(new UrlRoleAuthHandler());
return new UnanimousBased(decisionVoters);
}
@Autowired
private UrlRolesFilterHandler urlRolesFilterHandler;
class DefinedObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(urlRolesFilterHandler);
return object;
}
}
}