发布于2021-05-29 22:07 阅读(935) 评论(0) 点赞(13) 收藏(3)
目录
2.2 JwtRealm用于在登录之后,用户的token是否正确以及给当前用户授权等
2.3 OurModularRealmAuthenticator用于匹配的相应的Realm
2.5 JwtFilter处理在shiro配置的自定义的Filter
现在主流的安全框架分别为Shiro和Spring Security。关于两者之间的优缺点不是本文的重点,有兴趣的可以在网上搜搜,各种文章也都分析的很清楚。那么简单来说,Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。(不一定要建立所谓的五张表,我们要做到控制自如的使用)
通过集成shiro,jwt我们要实现:用户登录的校验;登录成功后返回成功并携带具有身份信息的token以便后续调用接口的时候做认证;对项目的接口进行权限的限定等。
本文使用的gradel作为jar包管理工具,maven也是使用相同的jar
- //shiro的jar
- implementation 'org.apache.shiro:shiro-spring:1.7.1'
- //jwt的jar
- implementation 'com.auth0:java-jwt:3.15.0'
- implementation 'com.alibaba:fastjson:1.2.76'
-
- compileOnly 'org.projectlombok:lombok'
- annotationProcessor 'org.projectlombok:lombok'
- @Configuration
- public class ShiroConfig {
- /*
- * 解决spring aop和注解配置一起使用的bug。如果您在使用shiro注解配置的同时,引入了spring
- * aop的starter,会有一个奇怪的问题,导致shiro注解的请求,不能被映射
- */
- @Bean
- public static DefaultAdvisorAutoProxyCreator creator() {
- DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
- creator.setProxyTargetClass(true);
- return creator;
- }
-
- /**
- * Enable Shiro AOP annotation support. --<1>
- *
- * @param securityManager Security Manager
- * @return AuthorizationAttributeSourceAdvisor
- */
- @Bean
- public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
- AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
- authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
- return authorizationAttributeSourceAdvisor;
- }
-
- /**
- * Use for login password matcher --<2>
- *
- * @return HashedCredentialsMatcher
- */
- @Bean("hashedCredentialsMatcher")
- public HashedCredentialsMatcher hashedCredentialsMatcher() {
- HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
- // set name of hash
- matcher.setHashAlgorithmName("SHA-256");
- // Storage format is hexadecimal
- matcher.setStoredCredentialsHexEncoded(true);
- return matcher;
- }
-
- /**
- * Realm for login --<3>
- *
- * @param matcher password matcher
- * @return PasswordRealm
- */
- @Bean
- public LoginRealm loginRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
- LoginRealm loginRealm = new LoginRealm(LOGIN);
- loginRealm.setCredentialsMatcher(matcher);
- return loginRealm;
- }
-
- /**
- * JwtReal, use for token validation --<4>
- *
- * @return JwtRealm
- */
- @Bean
- public JwtRealm jwtRealm() {
- return new JwtRealm(JWT);
- }
-
- // --<5>
- @Bean
- public OurModularRealmAuthenticator userModularRealmAuthenticator() {
- // rewrite ModularRealmAuthenticator
- DataAuthModularRealmAuthenticator modularRealmAuthenticator = new DataAuthModularRealmAuthenticator();
- modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
- return modularRealmAuthenticator;
- }
-
-
- // --<6>
- @Bean(name = "securityManager")
- public SecurityManager securityManager(
- @Qualifier("userModularRealmAuthenticator") OurModularRealmAuthenticatormodular,
- @Qualifier("jwtRealm") JwtRealm jwtRealm,
- @Qualifier("loginRealm") LoginRealm loginRealm
- ) {
- DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
- // set realm
- manager.setAuthenticator(modular);
- // set to use own realm
- List<Realm> realms = new ArrayList<>();
- realms.add(loginRealm);
- realms.add(jwtRealm);
- manager.setRealms(realms);
- // close Shiro's built-in session
- DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
- DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
- defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
- subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
- manager.setSubjectDAO(subjectDAO);
-
- return manager;
- }
-
- // --<7>
- @Bean(name = "shiroFilter")
- public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
- shiroFilterFactoryBean.setSecurityManager(securityManager);
- Map<String, Filter> filter = new LinkedHashMap<>(1);
- filter.put("jwt", new JwtFilter());
- shiroFilterFactoryBean.setFilters(filter);
-
- Map<String, String> filterMap = new HashMap<>();
- filterMap.put("/login/**", "anon");
- filterMap.put("/v1/**", "jwt");
- shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
- return shiroFilterFactoryBean;
- }
- }
开启shiro注解支持,具体原理请参考shiro中AuthorizationAttributeSourceAdvisor作用
配置shiro登录验证的密码加密方式:Shiro 提供了用于加密密码和验证密码服务的 CredentialsMatcher 接口,HashedCredentialsMatcher 正是 CredentialsMatcher 的一个实现类。
LoginRealm:自定义的Realm,用于处理用户登录验证的Realm,在shiro中验证及授权等信息会在Realm中配置,详细解释请参考shiro简介
JwtRealm:自定义的Realm,用户在登录后访问服务时做token的校验,用户权限的校验等。
配置DataAuthModularRealmAuthenticator:是在项目中存在多个Realm时,根据项目的认证策略可以选择匹配需要的Realm。
SecurityManager:Shiro的核心组件,管理着认证、授权、会话管理等,在这里我把所有的自定义的Realm等资源加入到SecurityManager中
Shiro的过滤器:定制项目的path过滤规则,并将我们自定义的Filter加入到Shiro中的shiroFilterFactoryBean中
- public class LoginRealm extends AuthorizingRealm {
- public LoginRealm(String name) {
- setName(name);
- }
-
- // 获取user相关信息的service类
- @Autowired
- private UserLoginService userLoginService;
-
- // supports方法必须重写,这是shiro处理流程中的一部分,他会通过此方法判断realm是否匹配的正确
- @Override
- public boolean supports(AuthenticationToken token) {
- return token instanceof LoginDataAutoToken;
- }
-
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- return null;
- }
-
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
- LoginDataAutoToken token = (LoginDataAutoToken) auth;
- serviceLog.info(token.getUsername() + "password auth start...");
- User user = userLoginService.selectUserByName(token.getUsername());
- if (user == null) throw new UnknownAccountException();
- Object credentials = user.getPassword();
- // save username and role to Attribute
- ServletUtils.userNameRoleTo.accept(user.getUserName(), (int) user.getUserType());
- return new SimpleAuthenticationInfo(user, credentials, super.getName());
- }
- }
- public class JwtRealm extends AuthorizingRealm {
- public JwtRealm(String name) {
- setName(name);
- }
-
- @Override
- public boolean supports(AuthenticationToken token) {
- return token instanceof JwtDataAutoToken;
- }
-
- // 给当前用户授权,只有在访问的接口上配置了shiro的权限相关的注解的时候才会进入此方法
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- UserEnum.Type userEnum = EnumValue.dataValueOf(
- UserEnum.Type.class,
- ServletUtils.userNameRoleFrom.get().getUserRole()
- );
- Set<String> roles = new HashSet<>();
- roles.add(userEnum.getDesc());
- // 授权角色如果有其他的权限则都已此类的方式授权
- authorizationInfo.setRoles(roles);
- return authorizationInfo;
- }
-
- // 验证此次request携带的token是否正确,如果正确解析当前token,并存入上下文中
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
- // verify token
- String token = (String) auth.getCredentials();
- TokenUtils.verify(token);
- TupleNameRole tupleNameRole = TokenUtils.tokenDecode(token);
- ServletUtils.userNameRoleTo.accept(tupleNameRole.getUsername(), tupleNameRole.getUserRole());
- return new SimpleAuthenticationInfo(token, token, ((JwtDataAutoToken) auth).getName());
- }
- }
- public class DataAuthModularRealmAuthenticator extends ModularRealmAuthenticator {
- @Override
- protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
- assertRealmsConfigured();
- DataAutoToken dataAutoToken = (DataAutoToken) authenticationToken;
-
- Realm realm = getRealm(dataAutoToken);
- return doSingleRealmAuthentication(realm, authenticationToken);
- }
-
- private Realm getRealm(DataAutoToken dataAutoToken) {
- for (Realm realm : getRealms()) {
- // 根据定义的realm的name和dataAutoToken的name匹配相应的realm
- if (realm.getName().contains(dataAutoToken.getName())) {
- return realm;
- }
- }
- return null;
- }
- }
DataAuthModularRealmAuthenticator的doSingleRealmAuthentication(realm, authenticationToken)做检验的时候需要两个参数,一个是Realm另一个是我们定义的储存验证信息的AuthenticationToken或者它的实现类。
DataAutoToken:
- public interface DataAutoToken {
- String getName();
- }
LoginDataAutoToken :
- public class LoginDataAutoToken extends UsernamePasswordToken implements DataAuthToken {
- public LoginDataAuthToken(final String username, final String password) {
- super(username, password);
- }
-
- @Override
- public String getName() {
- return LOGIN;
- }
- }
JwtDataAutoToken:
- public class JwtDataAutoToken implements AuthenticationToken, DataAuthToken {
- private final String token;
-
- public JwtDataAuthToken(String token) {
- this.token = token;
- }
-
- @Override
- public Object getPrincipal() {
- return token;
- }
-
- @Override
- public Object getCredentials() {
- return token;
- }
-
- @Override
- public String getName() {
- return JWT;
- }
- }
此类用于处理不在登录下必须携带发行的Token访问接口,如果Token存在,则使用shiro subject做token的和访问权限的校验。
- public class JwtFilter extends BasicHttpAuthenticationFilter {
- private final BiConsumer<ServletResponse, ErrorMessage> writeResponse = (response, message) ->
- Utils.renderString.accept(
- (HttpServletResponse) response,
- JSON.toJSONString(ResponseResult.fail(message), SerializerFeature.WriteMapNullValue)
- );
-
- /**
- * @param request ServletRequest
- * @param response ServletResponse
- * @param mappedValue mappedValue
- * @return 是否成功
- */
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- //input request to request log file
- requestLog.info(
- "path:{}, method:{}",
- httpServletRequest.getServletPath(),
- httpServletRequest.getMethod()
- );
- String token = httpServletRequest.getHeader(Constant.TOKEN);
- if (token != null) {
- return executeLogin(request, response);
- } else {
- writeResponse.accept(response, ErrorMessage.TOKEN_NOT_EXIST);
- return false;
- }
- }
-
- /**
- * execute login
- */
- @Override
- protected boolean executeLogin(ServletRequest request, ServletResponse response) {
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- String token = httpServletRequest.getHeader(Constant.TOKEN);
- try {
- JwtDataAuthToken jwtToken = new JwtDataAuthToken(token);
- // validate user permission
- getSubject(request, response).login(jwtToken);
- return true;
- } catch (AuthenticationException e) {
- Throwable throwable = e.getCause();
- if (throwable instanceof TokenExpiredException) {
- writeResponse.accept(response, ErrorMessage.TOKEN_HAS_EXPIRED);
- } else {
- writeResponse.accept(response, ErrorMessage.TOKEN_INVALID);
- }
- }
- return false;
- }
-
- /**
- * support across domains
- */
- @Override
- protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- HttpServletResponse httpServletResponse = (HttpServletResponse) response;
- httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
- httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
- httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
-
- if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
- httpServletResponse.setStatus(HttpStatus.OK.value());
- return false;
- }
- return super.preHandle(request, response);
- }
- @RestController
- public class AuthController {
- @Autowired
- private UserService userService;
-
- @PostMapping("/login")
- public ResponseResult<String> login(@RequestBody UserReqDto userReqDto) {
- userService.login(userLoginReqDto.getUsername(), userReqDto.getPassword());
- return ResponseResult.success();
- }
-
- // shiro角色注解,admin才可以访问此接口
- @RequiresRoles("admin")
- @PostMapping("/v1/user")
- public ResponseResult<String> addUser(@RequestBody UserAddReqDto userAddReqDto) {
- userService.add(userAddReqDto);
- return ResponseResult.success();
- }
-
-
- @PostMapping("/v1/token/verify")
- public ResponseResult<String> verify() {
- return ResponseResult.success(false);
- }
-
-
- @PostMapping("/v1/token/refresh")
- public ResponseResult<String> refresh() {
- return ResponseResult.success();
- }
- }
- @Service
- public class UserServiceImpl implements UserService {
- @Override
- public void login(String username, String password) {
- // Use shiro to verify the username and password
- Subject subject = SecurityUtils.getSubject();
- LoginDataAutoToken token = new LoginDataAutoToken(username, password);
- subject.login(token);
- }
-
- @Transactional
- @Override
- public void add(UserAddReqDto dto) {
- User user = getUserByName.apply(dto.getUsername());
- if (user != null) {
- throw new DataAuthException(ErrorMessage.USER_ALREADY_EXISTS);
- } else {
- User newUser = new User();
- // 设置user的信息
- post(newUser); // insert user to database
- }
- }
- public final class TokenUtils {
- private TokenUtils() {
- }
-
- /**
- * @param username username
- * @param role user role
- * @return The encrypted token
- */
- public static String createToken(String username, int role) {
- Date date = new Date(System.currentTimeMillis() + Constant.TOKEN_EXPIRE_TIME);
- Algorithm algorithm = Algorithm.HMAC256(username);
- return JWT.create()
- .withClaim(Constant.USER_NAME, username)
- .withClaim(Constant.USER_ROLE, role)
- .withExpiresAt(date)
- .sign(algorithm);
- }
-
- /**
- * @param username username
- * @param role user role
- * @return The encrypted token
- */
- public static String refreshToken(String username, int role) {
- return createToken(username, role);
- }
-
- /**
- * refresh token and add to header
- */
- public static void refreshToken() {
- TupleNameRole tupleNameRole = ServletUtils.userNameRoleFrom.get();
- ServletUtils.addHeader.accept(
- Constant.TOKEN,
- createToken(tupleNameRole.getUsername(), tupleNameRole.getUserRole())
- );
- }
-
- /**
- * verify token
- *
- * @param token jwtToken
- */
- public static void verify(String token) {
- try {
- TupleNameRole tupleNameRole = tokenDecode(token);
- Algorithm algorithm = Algorithm.HMAC256(tupleNameRole.getUsername());
- JWTVerifier verifier = JWT.require(algorithm)
- .withClaim(Constant.USER_NAME, tupleNameRole.getUsername())
- .withClaim(Constant.USER_ROLE, tupleNameRole.getUserRole())
- .build();
- verifier.verify(token);
- } catch (JWTVerificationException e) {
- serviceLog.error("token verify fail.", e);
- throw e;
- }
- }
-
- /**
- * @param token token
- * @return user name and role
- */
- public static TupleNameRole tokenDecode(String token) {
- try {
- DecodedJWT jwt = JWT.decode(token);
- return new TupleNameRole(
- jwt.getClaim(Constant.USER_NAME).asString(),
- jwt.getClaim(Constant.USER_ROLE).asInt()
- );
- } catch (JWTDecodeException e) {
- serviceLog.error("Token decode happen exception.", e);
- throw e;
- }
- }
- }
ServletUtils:与spring context中有关的一些方法
- public final class ServletUtils {
- private ServletUtils() {
- }
-
- private static final int SCOPE = RequestAttributes.SCOPE_REQUEST;
-
- private static final Supplier<ServletRequestAttributes> servletRequestAttributes = () ->
- (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-
- private static final Supplier<HttpServletRequest> request = () -> servletRequestAttributes.get().getRequest();
-
- private static final Supplier<HttpServletResponse> response = () -> servletRequestAttributes.get().getResponse();
-
- private static final Consumer<String> saveUsernameToAttribute = (name) ->
- servletRequestAttributes.get().setAttribute(Constant.USER_NAME, name, SCOPE);
-
- private static final Supplier<String> usernameFromAttribute = () ->
- (String) servletRequestAttributes.get().getAttribute(Constant.USER_NAME, SCOPE);
-
- private static final Consumer<Integer> saveUserRoleToAttribute = (role) ->
- servletRequestAttributes.get().setAttribute(Constant.USER_ROLE, role, SCOPE);
-
- private static final Supplier<Integer> userRoleFromAttribute = () ->
- (Integer) servletRequestAttributes.get().getAttribute(Constant.USER_ROLE, SCOPE);
-
- /**
- * get token form current request
- */
- public static Supplier<String> tokenFromRequest = () -> request.get().getHeader(Constant.TOKEN);
-
- /**
- * save current user name and role to attribute
- */
- public static BiConsumer<String, Integer> userNameRoleTo = (name, role) -> {
- saveUsernameToAttribute.accept(name);
- saveUserRoleToAttribute.accept(role);
- };
-
- /**
- * get user name and role from attribute
- */
- public static Supplier<TupleNameRole> userNameRoleFrom = () ->
- new TupleNameRole(usernameFromAttribute.get(), userRoleFromAttribute.get());
-
- /**
- * add message to response header
- */
- public static BiConsumer<String, String> addHeader = (key, value) -> response.get().addHeader(key, value);
- }
Utils:提供与shiro相同的密码加密方式、获取uuid、shiro的Filter层出错不能使用全局异常处理时的返回信息定制等。
- public final class Utils {
- private Utils() {
- }
-
- /**
- * use sha256 encrypt
- */
- public static Function<String, String> encryptPassword = (password) -> new Sha256Hash(password).toString();
-
- /**
- * get uuid
- */
- public static Supplier<String> uuid = () -> UUID.randomUUID().toString().replace("-", "");
-
- /**
- * writer message to response
- */
- public static BiConsumer<HttpServletResponse, String> renderString = (response, body) -> {
- response.setStatus(HttpStatus.OK.value());
- response.setCharacterEncoding("utf-8");
- response.setContentType("application/json;charset=UTF-8");
- try (PrintWriter writer = response.getWriter()) {
- writer.print(body);
- } catch (IOException e) {
- serviceLog.error("response error.", e);
- }
- };
- }
- @Data
- public class ResponseResult<T> implements Serializable {
- private static final long serialVersionUID = 1L;
-
- private final String code;
-
- @JSONField(ordinal = 1)
- private final String msg;
-
- @JSONField(ordinal = 2)
- private T data;
-
- private ResponseResult(String code, String msg) {
- this.code = code;
- this.msg = msg;
- log();
- }
-
- private static <T> ResponseResult<T> create(String code, String msg) {
- return new ResponseResult<>(code, msg);
- }
-
- /**
- * No data returned successfully
- *
- * @return ResponseResult<String>
- */
- public static <T> ResponseResult<T> success() {
- return success(true);
- }
-
- /**
- * No data returned successfully
- *
- * @param refreshToken Whether to refresh token
- * @return ResponseResult<String>
- */
- public static <T> ResponseResult<T> success(boolean refreshToken) {
- if (refreshToken) TokenUtils.refreshToken();
- return create(ErrorMessage.SUCCESS.code(), ErrorMessage.SUCCESS.msg());
- }
-
- public static <T> ResponseResult<T> success(T data) {
- return success(data, true);
- }
-
- /**
- * Data returned successfully
- *
- * @param data data
- * @param <T> T
- * @param refreshToken Whether to refresh token
- * @return ResponseResult<T>
- */
- public static <T> ResponseResult<T> success(T data, boolean refreshToken) {
- ResponseResult<T> responseResult = success(refreshToken);
- responseResult.setData(data);
- return responseResult;
- }
-
- /**
- * @param e DCException
- * @return ResponseResult<String>
- */
- public static ResponseResult<String> fail(DataAuthException e) {
- return create(e.getCode(), e.getMsg());
- }
-
- /**
- * @param errorMessage ErrorMessage
- * @return ResponseResult<String>
- */
- public static ResponseResult<String> fail(ErrorMessage errorMessage) {
- return create(errorMessage.code(), errorMessage.msg());
- }
-
- /**
- * @param errorMessage DCException
- * @return ResponseResult<String>
- */
- public static ResponseResult<String> fail(ErrorMessage errorMessage, Object[] detailMessage) {
- return create(errorMessage.code(), errorMessage.msg() + Arrays.toString(detailMessage));
- }
-
- // Output the information returned
- private void log() {
- requestLog.info("code:{}, msg:{}", this.getCode(), this.getMsg());
- }
- }
原文链接:https://blog.csdn.net/qq_35754073/article/details/117294507
作者:天花灯
链接:http://www.javaheidong.com/blog/article/207459/ecef980c2ada21d053c1/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!