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