1.数据库结构图:
2. SpringSecutiryConfiguration 此框架核心配置类
package com.pug.security;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SpringSecutiryConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private MyLogoutSuccessHandler myLogoutSuccessHandler;
@Autowired
private MyLoginSuccessHandler myLoginSuccessHandler;
@Autowired
private MyLoginFailHandler myLoginFailHandler;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
// 认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 1:基于接口认证
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 所有的请求开始接受认证处理
.authorizeRequests()
//不需要被认证
.antMatchers("/index", "/toLogin", "/fail", "/logout", "/nopermission")
// permitAll 放行不认证 只针对上permitAll()自身方法前定义的路径
.permitAll()
//.antMatchers("/user/list").hasRole("Admin")
//.antMatchers("/user/search").hasRole("User")
//.antMatchers("/user/get/**").hasAnyRole("User", "Admin")
//.antMatchers("/user/get/**").hasAuthority("user:list")
//所有请求都必须被认证,必须登录后被访问
.anyRequest().authenticated()
// 下一个配置 如果出现权限不足,403错误处理
.and().exceptionHandling().accessDeniedHandler(myAccessDeniedHandler)
// 下一个配置
.and().formLogin().loginProcessingUrl("/login")
.loginPage("/toLogin").usernameParameter("username")
.passwordParameter("password").successHandler(myLoginSuccessHandler)
.failureHandler(myLoginFailHandler).and().rememberMe()
.rememberMeParameter("remember-me").rememberMeCookieName("ksd-remember-me")
.tokenValiditySeconds(1800)
// 下一个配置
.and().logout().logoutSuccessHandler(myLogoutSuccessHandler)
.deleteCookies("ksd-remember-me", "JSESSIONID")
.and().csrf().disable();
}
// 放行资源
// 写到这里的资源放行,是不进入过滤器链的
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring()
.antMatchers
("/index.html", "/js/**", "/css/**", "/fonts/**", "/images/**", "/img/**");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// public static void main(String[] args) {
// System.out.println(new BCryptPasswordEncoder().encode("123456"));
// }
}
.antMatchers( “/toLogin”, “/fail”, “/logout”, “/nopermission”)
.and().formLogin().loginProcessingUrl("/login").loginPage("/toLogin")
所有request请求都需要被认证,除了 “/toLogin”, “/fail”, “/logout”, "/nopermission"这四个请求地址,
都会被重定向到http://localhost:8787/toLogin中!!!
控制层!!!
@Controller
public class LoginController {
@RequestMapping("/toLogin")
public String login() {
return "login";
}
}
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Please sign in</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
<div class="container">
<form class="form-signin" method="post" action="/login">
<h2 class="form-signin-heading" style="text-align:center ;">请登录</h2>
<p>
<label for="username" class="sr-only">账号</label>
<input type="text" id="username" name="username" class="form-control" placeholder="用户名" required autofocus>
</p>
<p>
<label for="password" class="sr-only">密码</label>
<input type="password" id="password" name="password" class="form-control" placeholder="密码" required>
</p>
<p><input type='checkbox' name='remember-me' checked/> 记住我</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
</form>
</div>
</body></html>
rememberMe()
.rememberMeParameter(“remember-me”).rememberMeCookieName(“ksd-remember-me”)
.tokenValiditySeconds(1800)
BCryptPasswordEncoder()加密的!!!
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("123456"));}
$2a 10 10 10JvQfsMscgToXnjzbHt89.ujHI5MFwTRFbXvVDo43046r2/AR0s1ey
.authorizeRequests()
//不需要被认证
.antMatchers( “/toLogin”, “/fail”, “/logout”, “/nopermission”)
// permitAll 放行不认证 只针对上permitAll()自身方法前定义的路径
.permitAll()
点击登录,必须认证!!!!
// 认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 1:基于接口认证
auth.userDetailsService(userDetailsService).(passwordEncoder());
}
自定义一个业务类,结合MybatisPlus,最关键是实现了UserDetailsService接口!!!
package com.pug.service.security;
@Service
@Slf4j
public class LoginUserServiceImpl extends
ServiceImpl<SysLoginUserMapper, SysLoginUser>
implements UserDetailsService, ILoginUserService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// 1: 设定查询条件
LambdaQueryWrapper<SysLoginUser> queryWrapper = new LambdaQueryWrapper<>();
// 2: 设置数据的用户名和输入用户进行where匹配
queryWrapper.eq(SysLoginUser::getUsername, username);
// 3: 执行查询
SysLoginUser sysLoginUser = this.getOne(queryWrapper);
// 4: 如果你查询用户不等于null
if (sysLoginUser != null) {
// 5:根据用户查询对应角色
List<SysRole> roleList = this.baseMapper.findSysRoleByUserId(sysLoginUser.getId());
// 6:根据用户查询对应权限
List<SysPermission> permissionList = this.baseMapper
.findByAdminUserId(sysLoginUser.getId());
// 7: 组装角色权限数据
// 7-1: 注意角色必须在这里增加ROLE_ 。不要在数据库里添加。因为根据灵活、
List<SimpleGrantedAuthority> roleAuthoritys =
roleList.stream().map(role -> "ROLE_" + role.getName())
//.map(SimpleGrantedAuthority::new)
.map(str -> new SimpleGrantedAuthority(str))
.collect(Collectors.toList());
// 8: 组装用户权限数据数据 。
// 8-1: 这里是根据权限名称
// 8-2: 这里是根据权限名称 + URL
List<SimpleGrantedAuthority> permissionAuthoritys = permissionList.stream()
.map(permission -> permission.getName())
//.map(SimpleGrantedAuthority::new)
.map(str -> new SimpleGrantedAuthority(str))
.collect(Collectors.toList());
// 9:组合
Set<GrantedAuthority> authorities =
new HashSet<>(roleList.size() + permissionList.size());
authorities.addAll(roleAuthoritys);
authorities.addAll(permissionAuthoritys);
// 10: 把用户查询的账号和密码,以及权限,和角色赋予SpringSecurity的UserDetails实现者User
LoginUser loginUser =
new LoginUser(sysLoginUser, roleList.stream().collect(Collectors.toSet()),
permissionList.stream().collect(Collectors.toSet()), authorities);
loginUser.setOs("window10");
loginUser.setIp("127.0.0.1");
loginUser.setIpLocation("guangzhou");
loginUser.setLoginTime(new Date().getTime()+"");
return loginUser;
} else {
throw new RuntimeException("用户名和密码有误!!!");
}
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.pug.mapper.SysLoginUserMapper">
<!--问:List<SysRole> 为什么可以使用SysRole-->
<select id="findSysRoleByUserId" resultType="com.pug.pojo.SysRole">
SELECT
sr.*
FROM
sys_role_user sru
LEFT JOIN sys_role sr ON sr.id = sru.sys_role_id
WHERE
sru.sys_user_id = #{userId}
</select>
<select id="findByAdminUserId" resultType="com.pug.pojo.SysPermission">
SELECT
p.*
FROM
sys_role_user sru
LEFT JOIN Sys_Role r ON sru.Sys_Role_id = r.id
LEFT JOIN Sys_permission_role spr ON spr.role_id = r.id
LEFT JOIN Sys_permission p ON p.id = spr.permission_id
WHERE
sru.sys_user_id = #{userId}
</select>
</mapper>
.and().formLogin().loginProcessingUrl("/login").loginPage("/toLogin").usernameParameter(“username”).passwordParameter(“password”).successHandler(myLoginSuccessHandler).failureHandler(myLoginFailHandler).
认证成功的话 MyLoginSuccessHandler myLoginSuccessHandler
@Component
public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//User user = (User) authentication.getPrincipal();
//request.getSession().setAttribute("sessionuser", user);
// 生成token
response.sendRedirect("/index");
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>我是后台首页</h1>
<h3>你当前登录的信息是:{{user.username}} === {{roles}}</h3>
<div>{{permissions}}</div>
<a href="/logout">退出</a>
<div>
<a href="/user/get/1">查询用户</a>
<a href="/user/search">搜索用户</a>
<a href="/user/list">用户列表</a>
</div>
</div>
<script src="/js/vue.min.js"></script>
<script src="/js/axios.min.js"></script>
<script>
new Vue({
el:"#app",
data:{
user:{},
roles:[],
permissions:[],
},
created(){
this.loadUser();
},
methods:{
loadUser(){
axios.post("/username").then(res=>{
this.user = res.data;
this.roles = this.user.roles.map(role=>role.title).join(",");
this.permissions = this.user.permissions;
})
}
}
})
</script>
</body>
</html>
认证失败的话MyLoginFailHandler myLoginFailHandler
@Component
@Slf4j
public class MyLoginFailHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("失败的原因是:{}", exception.getMessage());
// 生成token
response.sendRedirect("/fail?msg="+exception.getMessage());
}
}
最核心的是自己改写了User类如下:
public class LoginUser implements UserDetails {
private Long userId;
private String username;
private String ip;
private String ipLocation;
private String os;
private String loginTime;
private SysLoginUser sysLoginUser;
private Set<SysRole> roles;
private Set<SysPermission> permissions;
private Set<GrantedAuthority> authorities;
public LoginUser(SysLoginUser sysLoginUser,Set<SysRole> roles,Set<SysPermission> permissions,
Set<GrantedAuthority> authorities){
this.userId = sysLoginUser.getId();
this.username = sysLoginUser.getUsername();
this.sysLoginUser = sysLoginUser;
this.roles = roles;
this.permissions = permissions;
this.authorities = authorities;
}
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@JsonIgnore
@Override
public String getPassword() {
return sysLoginUser.getPassword();
}
@Override
public String getUsername() {
return sysLoginUser.getUsername();
}
private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
// Ensure array iteration order is predictable (as per
// UserDetails.getAuthorities() contract and SEC-717)
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new LoginUser.AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
@Override
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
if (g2.getAuthority() == null) {
return -1;
}
if (g1.getAuthority() == null) {
return 1;
}
return g1.getAuthority().compareTo(g2.getAuthority());
}
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getIpLocation() {
return ipLocation;
}
public void setIpLocation(String ipLocation) {
this.ipLocation = ipLocation;
}
public String getOs() {
return os;
}
public void setOs(String os) {
this.os = os;
}
public String getLoginTime() {
return loginTime;
}
public void setLoginTime(String loginTime) {
this.loginTime = loginTime;
}
@JsonIgnore
public SysLoginUser getSysLoginUser() {
return sysLoginUser;
}
public void setSysLoginUser(SysLoginUser sysLoginUser) {
this.sysLoginUser = sysLoginUser;
}
public Set<SysRole> getRoles() {
return roles;
}
public void setRoles(Set<SysRole> roles) {
this.roles = roles;
}
public Set<SysPermission> getPermissions() {
return permissions;
}
public void setPermissions(Set<SysPermission> permissions) {
this.permissions = permissions;
}
public void setAuthorities(Set<GrantedAuthority> authorities) {
this.authorities = authorities;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "LoginUser{" +
"userId=" + userId +
", ip='" + ip + '\'' +
", ipLocation='" + ipLocation + '\'' +
", os='" + os + '\'' +
", loginTime='" + loginTime + '\'' +
", sysLoginUser=" + sysLoginUser +
", roles=" + roles +
", permissions=" + permissions +
", authorities=" + authorities +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LoginUser loginUser = (LoginUser) o;
return Objects.equals(userId, loginUser.userId) && Objects.equals(ip, loginUser.ip) && Objects.equals(ipLocation, loginUser.ipLocation) && Objects.equals(os, loginUser.os) && Objects.equals(loginTime, loginUser.loginTime) && Objects.equals(sysLoginUser, loginUser.sysLoginUser) && Objects.equals(roles, loginUser.roles) && Objects.equals(permissions, loginUser.permissions) && Objects.equals(authorities, loginUser.authorities);
}
@Override
public int hashCode() {
return Objects.hash(userId, ip, ipLocation, os, loginTime, sysLoginUser, roles, permissions, authorities);
}
}
配置类方式
//.antMatchers("/user/list").hasRole(“User”)
//.antMatchers("/user/search").hasRole(“Admin”)
//.antMatchers("/user/get/**").hasAuthority(“user:list”)
注解方式:
@RestController
public class UserController {
@Autowired
private ILoginUserService loginUserService;
@GetMapping("/user/get/{id}")
@PreAuthorize("hasAnyRole('Admin','User')")
public SysLoginUser getUser(@PathVariable("id") Long id) {
return loginUserService.getById(id);
}
@GetMapping("/user/list")
@PreAuthorize("hasRole('User')")
public List<SysLoginUser> listuser() {
return loginUserService.list();
}
@GetMapping("/user/search")
@PreAuthorize("hasAuthority('course:list')")
public List<SysLoginUser> searchuser() {
return loginUserService.list();
}
}
admin用户点击搜索:
// 下一个配置 如果出现权限不足,403错误处理
.and().exceptionHandling().accessDeniedHandler(myAccessDeniedHandler)
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// response.setHeader("Content-Type", "application/json;charset=utf-8");
// PrintWriter out = response.getWriter();
// out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
// out.flush();
// out.close();
response.sendRedirect("/nopermission");
}
}
nopermission.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>没权限</title>
</head>
<body>
<h1>没有权限,请联系管理员</h1>
<a href="/">返回首页</a>
</body>
</html>
难点:
1.SpringSecutiryConfiguration 配置类的书写
2.两个业务层代码的书写