mail_yanpeng@163.com
4 years ago
16 changed files with 442 additions and 5 deletions
@ -0,0 +1,13 @@ |
|||||
|
package com.aiprose.scauth.controller; |
||||
|
|
||||
|
import org.springframework.stereotype.Controller; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
|
||||
|
@Controller |
||||
|
public class HomeController { |
||||
|
|
||||
|
@GetMapping("/home") |
||||
|
public String home(){ |
||||
|
return "home"; |
||||
|
} |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
package com.aiprose.scauth.controller; |
||||
|
|
||||
|
import org.springframework.stereotype.Controller; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
|
||||
|
@Controller |
||||
|
public class LoginController { |
||||
|
|
||||
|
@GetMapping("/login") |
||||
|
public String login(){ |
||||
|
return "login"; |
||||
|
} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package com.aiprose.scauth.entity; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.springframework.security.core.GrantedAuthority; |
||||
|
|
||||
|
import javax.persistence.Entity; |
||||
|
import javax.persistence.Table; |
||||
|
|
||||
|
/** |
||||
|
* @author nelson |
||||
|
* @desc 角色表 |
||||
|
* @company 北京中经网软件有限公司 |
||||
|
* @date 2020/11/27 17:04 |
||||
|
* @since 1.0 |
||||
|
*/ |
||||
|
@Data |
||||
|
@Entity |
||||
|
@Table(name = "sys_menu") |
||||
|
public class Menu extends IDEntity { |
||||
|
private String url; |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package com.aiprose.scauth.entity; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.persistence.Entity; |
||||
|
import javax.persistence.Table; |
||||
|
|
||||
|
/** |
||||
|
* @author nelson |
||||
|
* @desc 角色菜单关联表 |
||||
|
* @company 北京中经网软件有限公司 |
||||
|
* @date 2020/11/27 17:04 |
||||
|
* @since 1.0 |
||||
|
*/ |
||||
|
@Data |
||||
|
@Entity |
||||
|
@Table(name="sys_role_menu") |
||||
|
public class RoleMenu extends IDEntity{ |
||||
|
private Integer sysRoleId; |
||||
|
private Integer sysMenuId; |
||||
|
|
||||
|
} |
@ -0,0 +1,103 @@ |
|||||
|
package com.aiprose.scauth.handler; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.StringRedisTemplate; |
||||
|
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; |
||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Component |
||||
|
public class RemeberMeHandler implements PersistentTokenRepository { |
||||
|
/** |
||||
|
* token有效时间30天 |
||||
|
*/ |
||||
|
private static final Long TOKEN_VALID_DAYS = 15L; |
||||
|
|
||||
|
@Autowired |
||||
|
private StringRedisTemplate stringRedisTemplate; |
||||
|
|
||||
|
@Override |
||||
|
public void createNewToken(PersistentRememberMeToken token) { |
||||
|
String key = generateTokenKey(token.getSeries()); |
||||
|
Map<String,String> map = new HashMap<>(8); |
||||
|
map.put("username", token.getUsername()); |
||||
|
map.put("tokenValue", token.getTokenValue()); |
||||
|
map.put("date", String.valueOf(token.getDate().getTime())); |
||||
|
stringRedisTemplate.opsForHash().putAll(key, map); |
||||
|
stringRedisTemplate.expire(key,TOKEN_VALID_DAYS, TimeUnit.DAYS); |
||||
|
|
||||
|
saveUsernameAndSeries(token.getUsername(), token.getSeries()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void updateToken(String series, String tokenValue, Date lastUsed) { |
||||
|
String key = generateTokenKey(series); |
||||
|
Boolean hasSeries = stringRedisTemplate.hasKey(key); |
||||
|
if(hasSeries==null || !hasSeries){ |
||||
|
return; |
||||
|
} |
||||
|
Map<String,String> map = new HashMap<>(4); |
||||
|
map.put("tokenValue", tokenValue); |
||||
|
map.put("date", String.valueOf(lastUsed.getTime())); |
||||
|
stringRedisTemplate.opsForHash().putAll(key, map); |
||||
|
stringRedisTemplate.expire(key, TOKEN_VALID_DAYS, TimeUnit.DAYS); |
||||
|
|
||||
|
String username = stringRedisTemplate.opsForValue().get(generateUsernameAndSeriesKey(series)); |
||||
|
saveUsernameAndSeries(username, series); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public PersistentRememberMeToken getTokenForSeries(String seriesId) { |
||||
|
String key = generateTokenKey(seriesId); |
||||
|
Map hashKeyValues = stringRedisTemplate.opsForHash().entries(key); |
||||
|
if (hashKeyValues == null) { |
||||
|
return null; |
||||
|
} |
||||
|
Object username = hashKeyValues.get("username"); |
||||
|
Object tokenValue = hashKeyValues.get("tokenValue"); |
||||
|
Object date = hashKeyValues.get("date"); |
||||
|
if (null == username || null == tokenValue || null == date) { |
||||
|
return null; |
||||
|
} |
||||
|
long timestamp = Long.valueOf(String.valueOf(date)); |
||||
|
Date time = new Date(timestamp); |
||||
|
|
||||
|
return new PersistentRememberMeToken(String.valueOf(username), seriesId, String.valueOf(tokenValue), time); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void removeUserTokens(String username) { |
||||
|
String series = stringRedisTemplate.opsForValue().get(generateUsernameAndSeriesKey(username)); |
||||
|
if (series==null||series.trim().length()<=0){ |
||||
|
return; |
||||
|
} |
||||
|
stringRedisTemplate.delete(generateTokenKey(series)); |
||||
|
stringRedisTemplate.delete(generateUsernameAndSeriesKey(username)); |
||||
|
stringRedisTemplate.delete(generateUsernameAndSeriesKey(series)); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void saveUsernameAndSeries(String username, String series){ |
||||
|
stringRedisTemplate.opsForValue().set(generateUsernameAndSeriesKey(username),series,TOKEN_VALID_DAYS*2, TimeUnit.DAYS); |
||||
|
stringRedisTemplate.opsForValue().set(generateUsernameAndSeriesKey(series),username,TOKEN_VALID_DAYS*2, TimeUnit.DAYS); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成token key |
||||
|
*/ |
||||
|
private String generateTokenKey(String series) { |
||||
|
return "spring:security:rememberMe:token:" + series; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成key |
||||
|
*/ |
||||
|
private String generateUsernameAndSeriesKey(String usernameOrSeries) { |
||||
|
return "spring:security:rememberMe:"+usernameOrSeries; |
||||
|
} |
||||
|
} |
@ -0,0 +1,58 @@ |
|||||
|
package com.aiprose.scauth.handler; |
||||
|
|
||||
|
import org.springframework.security.access.AccessDecisionVoter; |
||||
|
import org.springframework.security.access.ConfigAttribute; |
||||
|
import org.springframework.security.core.Authentication; |
||||
|
import org.springframework.security.core.GrantedAuthority; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
|
||||
|
/** |
||||
|
* 角色 权限 路由处理 |
||||
|
*/ |
||||
|
public class UrlRoleAuthHandler implements AccessDecisionVoter<Object> { |
||||
|
@Override |
||||
|
public boolean supports(ConfigAttribute attribute) { |
||||
|
if (null == attribute.getAttribute()) { |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean supports(Class<?> clazz) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* ACCESS_GRANTED – 同意 |
||||
|
* ACCESS_DENIED – 拒绝 |
||||
|
* ACCESS_ABSTAIN – 弃权 |
||||
|
*/ |
||||
|
@Override |
||||
|
public int vote(Authentication user, Object object, Collection<ConfigAttribute> urlRoles) { |
||||
|
if (null == user) { |
||||
|
return ACCESS_DENIED; |
||||
|
} |
||||
|
|
||||
|
int result = ACCESS_ABSTAIN; |
||||
|
Collection<? extends GrantedAuthority> userRoles = user.getAuthorities(); |
||||
|
|
||||
|
/* 遍历链接中对应的权限 */ |
||||
|
for (ConfigAttribute urlRole : urlRoles) { |
||||
|
if (this.supports(urlRole)) { |
||||
|
/* 此处默认值为弃权,表示只要有一个角色对应上,用户就可以访问链接 |
||||
|
如果值改为拒绝,表示必须全部角色包含才能访问链接 |
||||
|
*/ |
||||
|
result = ACCESS_ABSTAIN; |
||||
|
/* 遍历用户中对应的角色列表 */ |
||||
|
for (GrantedAuthority userRole : userRoles) { |
||||
|
if (urlRole.getAttribute().equals(userRole.getAuthority())) { |
||||
|
return ACCESS_GRANTED; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
package com.aiprose.scauth.handler; |
||||
|
|
||||
|
import com.aiprose.scauth.service.IRoleService; |
||||
|
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.Service; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Service |
||||
|
public class UrlRolesFilterHandler implements FilterInvocationSecurityMetadataSource { |
||||
|
|
||||
|
@Autowired |
||||
|
private IRoleService roleService; |
||||
|
|
||||
|
@Override |
||||
|
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { |
||||
|
String requestUrl = ((FilterInvocation) object).getRequestUrl(); |
||||
|
List<String> roleNames = roleService.findByUrl(requestUrl); |
||||
|
String[] names = new String[roleNames.size()]; |
||||
|
for (int i = 0; i < roleNames.size(); i++) { |
||||
|
names[i] = roleNames.get(i); |
||||
|
} |
||||
|
return SecurityConfig.createList(names); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Collection<ConfigAttribute> getAllConfigAttributes() { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean supports(Class<?> clazz) { |
||||
|
return FilterInvocation.class.isAssignableFrom(clazz); |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
package com.aiprose.scauth.repository; |
||||
|
|
||||
|
import com.aiprose.scauth.entity.Menu; |
||||
|
import com.aiprose.scauth.entity.User; |
||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; |
||||
|
|
||||
|
/** |
||||
|
* @author nelson |
||||
|
* @desc TODO |
||||
|
* @company 北京中经网软件有限公司 |
||||
|
* @date 2020/11/27 17:17 |
||||
|
* @since 1.0 |
||||
|
*/ |
||||
|
public interface MenuRepository extends JpaRepository<Menu, Integer>, JpaSpecificationExecutor<Menu> { |
||||
|
|
||||
|
Menu findByUrl(String url); |
||||
|
} |
@ -0,0 +1,20 @@ |
|||||
|
package com.aiprose.scauth.repository; |
||||
|
|
||||
|
import com.aiprose.scauth.entity.RoleMenu; |
||||
|
import com.aiprose.scauth.entity.User; |
||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @author nelson |
||||
|
* @desc TODO |
||||
|
* @company 北京中经网软件有限公司 |
||||
|
* @date 2020/11/27 17:17 |
||||
|
* @since 1.0 |
||||
|
*/ |
||||
|
public interface RoleMenuRepository extends JpaRepository<RoleMenu, Integer>, JpaSpecificationExecutor<RoleMenu> { |
||||
|
|
||||
|
List<RoleMenu> findBySysMenuId(Integer menuId); |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html xmlns:th="https://www.thymeleaf.org"> |
||||
|
<head> |
||||
|
<title>请登录</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div> |
||||
|
<form th:action="@{/login}" method="post" action="/login"> |
||||
|
<p> |
||||
|
<span>用户名:</span> |
||||
|
<input type="text" id="username" name="username"> |
||||
|
</p> |
||||
|
<p> |
||||
|
<span>密码:</span> |
||||
|
<input type="password" id="password" name="password"> |
||||
|
</p> |
||||
|
|
||||
|
<p> |
||||
|
<span>记住我:</span> |
||||
|
<input type="checkbox" name="remember-me" value="true"> |
||||
|
</p> |
||||
|
|
||||
|
<!-- 不使用 th:action 属性 和 不关闭csrf 的情况下,需要放开下面的标签 --> |
||||
|
<!--<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}"/>--> |
||||
|
|
||||
|
<input type="submit" value="登录" /> |
||||
|
</form> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
Loading…
Reference in new issue